Quickstart
Submit a tool run, wait for completion, fetch results, and download generated files.
This quickstart runs a complete API workflow: list tools, inspect a tool, submit a job, wait for completion, fetch results, and download the first generated file.
You need a paid workspace and an API key created in API keys. See Authentication for key format, scopes, and storage guidance.
Set credentials
Store your API key in an environment variable. Do not hard-code API keys in source files, notebooks that will be shared, or browser code.
export PROTEINIQ_API_KEY="pq_live_abc123_your_secret"
export PROTEINIQ_BASE_URL="https://proteiniq.io"List tools
Use the tool catalog to find valid tool ids.
curl -s \
-H "Authorization: Bearer $PROTEINIQ_API_KEY" \
"$PROTEINIQ_BASE_URL/api/v1/tools"The response is a list resource:
{
"object": "list",
"data": [
{
"id": "esmfold",
"object": "tool",
"name": "ESMFold",
"category": "folding",
"credit_model": {
"is_free": false
}
}
],
"has_more": false,
"next_cursor": null
}Inspect a tool
Describe the tool before sending input. The detail response includes input, output, limits, processing, and credit metadata.
curl -s \
-H "Authorization: Bearer $PROTEINIQ_API_KEY" \
"$PROTEINIQ_BASE_URL/api/v1/tools/esmfold"Submit a job
Submit JSON input and include an Idempotency-Key when retrying client-side requests. The same key is scoped to your workspace and prevents duplicate job creation for repeated submit requests.
curl -s -X POST \
-H "Authorization: Bearer $PROTEINIQ_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: fold-lysozyme-001" \
"$PROTEINIQ_BASE_URL/api/v1/jobs" \
-d '{
"tool": "esmfold",
"name": "Lysozyme ESMFold run",
"input": {
"inputs": [
{
"id": "seqs_1",
"slotId": "protein",
"kind": "protein",
"format": "fasta",
"content": ">lysozyme\nKVFGRCELAAAMKRHGLDNYRGYSLGNWVCAAKFESNFNTQATNRNTDGSTDYGILQINSR",
"source": { "type": "text" }
}
]
},
"settings": {}
}'The response is a job resource. Save the id field.
{
"id": "job_123",
"object": "job",
"status": "PENDING",
"tool": "esmfold",
"name": "Lysozyme ESMFold run",
"credits_used": 50,
"created_at": "2026-06-12T08:00:00.000Z",
"started_at": null,
"completed_at": null,
"progress": 0,
"execution_time_seconds": null,
"error": null
}Wait for completion
Poll the status endpoint until the job reaches a terminal status.
curl -s \
-H "Authorization: Bearer $PROTEINIQ_API_KEY" \
"$PROTEINIQ_BASE_URL/api/v1/jobs/$JOB_ID/status"You can also stream server-sent events:
curl -N \
-H "Authorization: Bearer $PROTEINIQ_API_KEY" \
"$PROTEINIQ_BASE_URL/api/v1/jobs/$JOB_ID/events"Treat COMPLETED, FAILED, TIMEOUT, CANCELLED, and BUDGET_EXCEEDED as terminal statuses. Start polling every 5 seconds, then back off to 10 seconds, 20 seconds, and a 30 second cap. If the API returns Retry-After, wait at least that many seconds before the next status or result request.
Do not replace status polling with a fixed sleep before fetching results. Job runtime and result persistence vary by tool and input size; /api/v1/results/{jobId} returns job_not_completed until the job is result-ready.
Fetch results
Fetch the result after the job is result-ready.
curl -s \
-H "Authorization: Bearer $PROTEINIQ_API_KEY" \
"$PROTEINIQ_BASE_URL/api/v1/results/$JOB_ID" \
-o result.jsonDownload the first generated file from its signed URL:
curl -L "$(jq -r '.files[0].url' result.json)" -o result-fileCode examples
The Python example uses only the standard library. The JavaScript example uses built-in fetch in modern Node.js runtimes.
import json
import os
import random
import time
import urllib.request
base_url = os.environ.get("PROTEINIQ_BASE_URL", "https://proteiniq.io")
api_key = os.environ["PROTEINIQ_API_KEY"]
def request_json(method, path, body=None, headers=None):
data = None if body is None else json.dumps(body).encode("utf-8")
req = urllib.request.Request(
f"{base_url}{path}",
data=data,
method=method,
headers={
"Authorization": f"Bearer {api_key}",
**({"Content-Type": "application/json"} if body is not None else {}),
**(headers or {}),
},
)
with urllib.request.urlopen(req) as response:
return json.loads(response.read().decode("utf-8")), response.headers
job, _ = request_json(
"POST",
"/api/v1/jobs",
{
"tool": "esmfold",
"name": "Lysozyme ESMFold run",
"input": {
"inputs": [
{
"id": "seqs_1",
"slotId": "protein",
"kind": "protein",
"format": "fasta",
"content": ">lysozyme\nKVFGRCELAAAMKRHGLDNYRGYSLGNWVCAAKFESNFNTQATNRNTDGSTDYGILQINSR",
"source": {"type": "text"},
}
]
},
"settings": {},
},
headers={"Idempotency-Key": "fold-lysozyme-001"},
)
terminal = {"COMPLETED", "FAILED", "TIMEOUT", "CANCELLED", "BUDGET_EXCEEDED"}
poll_delay = 5
while job["status"] not in terminal:
time.sleep(poll_delay + random.uniform(0, 1))
job, headers = request_json("GET", f"/api/v1/jobs/{job['id']}/status")
retry_after = int(headers.get("Retry-After") or 0)
poll_delay = min(max(poll_delay * 2, retry_after), 30)
if job["status"] != "COMPLETED":
raise RuntimeError(f"Job ended with status {job['status']}: {job.get('error')}")
result, _ = request_json("GET", f"/api/v1/results/{job['id']}")
print(result["files"])