Jobs
Quote, submit, and follow individual tool runs.
A job is one execution of a tool with specific inputs and settings. API jobs use the same workspace credits, permissions, limits, and result storage model as jobs created in the web app.
Quote a job
Use POST /api/v1/jobs/quote with jobs:write to validate a payload and estimate credits without creating a job.
curl --fail-with-body --silent --show-error -X POST \
-H "Authorization: Bearer $PROTEINIQ_API_KEY" \
-H "Content-Type: application/json" \
"https://proteiniq.io/api/v1/jobs/quote" \
-d '{
"tool": "esmfold",
"input": {
"inputs": [
{
"id": "seqs_1",
"slotId": "protein",
"kind": "protein",
"format": "fasta",
"content": ">example\nACDEFGHIKLMNPQRSTVWY",
"source": { "type": "text" }
}
]
},
"settings": {}
}'Quote responses include credit estimates, available credits, blocking_errors, current limits, and billing mode. Resolve blocking errors before submission. The values below are illustrative; use your response for current costs and limits.
Quotes are optional estimates. They do not reserve credits or retain a prepared submission.
{
"object": "job_quote",
"tool": "esmfold",
"estimated_credits": 50,
"available_credits": 500,
"billable_credits": 50,
"blocking_errors": [],
"limits": {
"active_concurrent_jobs": 0,
"max_concurrent_jobs": 3,
"daily_limit_used": 2,
"daily_limit_max": 100
},
"billing": {
"mode": "fixed"
}
}Submit a job
Use POST /api/v1/jobs with jobs:write to create a job. Direct submissions require JSON with tool, name, and input; settings, billing, and project_id are optional. Assigning a project also requires projects:write.
Quote and submission bodies are limited to 512 KiB. After resolving saved file references, combined input content is limited to 50 MiB. Upload larger inputs and reference them with source.file_id; that also requires files:read.
{
"tool": "esmfold",
"name": "ESMfold API example",
"input": {
"inputs": [
{
"id": "seqs_1",
"slotId": "protein",
"kind": "protein",
"format": "fasta",
"content": ">example\nACDEFGHIKLMNPQRSTVWY",
"source": { "type": "text" }
}
]
},
"settings": {}
}Set PROTEINIQ_RUN_KEY to a unique value for each intended analysis, as in the quickstart. Send it as Idempotency-Key or X-Idempotency-Key when submitting or retrying. The same key is scoped to the workspace.
Retry with the same request body and key. Changed input, settings, or credit limits require a new key.
To protect against a price increase after quoting, include max_quoted_credits in the submission body.
This nonnegative integer limits the fixed charge or initial runtime reservation. If the current amount exceeds it,
submission returns 409 with price_changed before creating or charging a job. For runtime billing,
billing.max_reserved_credits separately limits the total reservation; max_quoted_credits is not a total runtime budget.
curl --fail-with-body --silent --show-error -X POST \
-H "Authorization: Bearer $PROTEINIQ_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $PROTEINIQ_RUN_KEY" \
"https://proteiniq.io/api/v1/jobs" \
-d '{
"tool": "esmfold",
"name": "ESMfold API example",
"input": {
"inputs": [
{
"id": "seqs_1",
"slotId": "protein",
"kind": "protein",
"format": "fasta",
"content": ">example\nACDEFGHIKLMNPQRSTVWY",
"source": { "type": "text" }
}
]
},
"settings": {}
}'Submit returns a job resource with status code 202 for new jobs. Replayed idempotent submissions return the stored response status and include a top-level message such as Duplicate suppressed via idempotency key; that replay-only field is not present on status, list, or result resources.
Input validation
Both quote and submit requests accept only input.inputs[]. Each item must include id, slotId, kind, format, content, and source. A saved file reference may omit content only when its file source includes file_id or fileId.
Unknown fields and retired input shapes are rejected, including requests that mix valid canonical inputs with retired fields. For example, this request is rejected:
{
"tool": "esmfold",
"name": "Invalid input example",
"input": {
"molecules": []
},
"settings": {}
}The API returns a path-specific response before pricing or job creation:
{
"error": {
"code": "validation_error",
"message": "input.molecules: unsupported legacy field \"molecules\"; send input.inputs[] with slotId, kind, format, content, and source"
}
}Do not remove only the reported field from a mixed payload. Rebuild the request from the active input.slots[] contract returned by the tool endpoint.
Job resource
Single-job endpoints return this resource shape:
{
"id": "job_123",
"object": "job",
"project_id": "project_123",
"status": "PROCESSING",
"tool": "esmfold",
"name": "ESMfold API example",
"credits_used": 50,
"created_at": "2026-06-12T08:00:00.000Z",
"started_at": "2026-06-12T08:00:02.000Z",
"completed_at": null,
"progress": 40,
"execution_time_seconds": null,
"error": null,
"billing": {
"mode": "fixed",
"reserved_credits": null,
"final_credits": null,
"rate_credits_per_minute": null,
"billable_runtime_seconds": null,
"outcome": null,
"finalized_at": null
}
}The billing object is included when billing metadata exists for the job.
List jobs
GET /api/v1/jobs lists jobs in the API key workspace.
curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer $PROTEINIQ_API_KEY" \
"https://proteiniq.io/api/v1/jobs?limit=20"List jobs supports:
limit: Integer from1to100, defaults to20starting_after: Cursor returned asnext_cursorfrom a previous pageproject_id: Return jobs assigned to one projectunassigned: Set totrueto return jobs without a project
Do not combine project_id and unassigned=true.
The response is ordered by newest job first.
{
"object": "list",
"data": [],
"has_more": false,
"next_cursor": null
}Get job status
Status and list requests require jobs:read.
GET /api/v1/jobs/{jobId}/status returns the current job resource.
curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer $PROTEINIQ_API_KEY" \
"https://proteiniq.io/api/v1/jobs/job_123/status"Poll while the status is PENDING, QUEUED, PROCESSING, or RETRY.
Terminal statuses describe different outcomes:
COMPLETED: The job finished successfully.FAILED: The job failed; inspecterrorand any available partial results.TIMEOUT: The job exceeded its allowed time.CANCELLED: The job was canceled.BUDGET_EXCEEDED: A spending or runtime limit stopped the job; partial outputs may be available.
A terminal status does not guarantee downloadable output. Follow the result availability rules.
Share a job
GET /api/v1/jobs/{jobId}/share returns the current sharing settings for a job in the API key workspace. The endpoint requires jobs:read.
curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer $PROTEINIQ_API_KEY" \
"https://proteiniq.io/api/v1/jobs/job_123/share"The response includes the job visibility and invited email shares.
{
"object": "job_sharing",
"job_id": "job_123",
"visibility": "INVITED",
"shares": [
{
"id": "share_123",
"object": "job_share",
"email": "collaborator@example.com",
"claimed": true,
"claimed_at": "2026-06-12T08:01:00.000Z",
"invitee_name": "Collaborator",
"created_at": "2026-06-12T08:00:00.000Z"
}
]
}PATCH /api/v1/jobs/{jobId}/share updates the same sharing settings available in the web app. The endpoint requires jobs:write, and the API key creator must have permission to update the job.
curl --fail-with-body --silent --show-error -X PATCH \
-H "Authorization: Bearer $PROTEINIQ_API_KEY" \
-H "Content-Type: application/json" \
"https://proteiniq.io/api/v1/jobs/job_123/share" \
-d '{
"visibility": "INVITED",
"add_invites": ["collaborator@example.com"],
"remove_invites": ["old-collaborator@example.com"]
}'Visibility values are:
PRIVATE: Keeps access within normal workspace job permissionsPUBLIC: Allows anyone with the job link to view the jobAUTHENTICATED: Allows any signed-in ProteinIQ user to view the jobINVITED: Allows invited email addresses to view the job, alongside normal workspace job permissions
Update fields are:
visibility: One ofPRIVATE,PUBLIC,AUTHENTICATED, orINVITEDadd_invites: Array of email addresses to add to the invited listremove_invites: Array of email addresses to remove from the invited list
New invitations can be added only when the job is already INVITED or the same request sets visibility to INVITED. ProteinIQ normalizes invitation addresses and emails each newly invited person a direct result link.
Snake_case field names are preferred. The API also accepts addInvites and removeInvites aliases for clients that share request code with the web app.
Polling strategy
Use exponential backoff when polling job status. Start with a 5 second delay, then increase to 10 seconds, 20 seconds, and cap at 30 seconds. Add a small random jitter so many jobs submitted at the same time do not poll in lockstep. When a response includes Retry-After, wait at least that many seconds before the next request.
Do not poll /status faster than once every 5 seconds for the same job. Fast polling does not make a job finish sooner, counts against the public API rate limit, and may return rate_limited. For near-real-time updates, use job events.
Fetch results after an eligible terminal state: COMPLETED, BUDGET_EXCEEDED, or FAILED with stored output. If a result request returns job_not_completed, wait for the Retry-After value before trying again.
Cancel a job
POST /api/v1/jobs/{jobId}/cancel cancels a pending or queued job when cancellation is still allowed.
curl --fail-with-body --silent --show-error -X POST \
-H "Authorization: Bearer $PROTEINIQ_API_KEY" \
"https://proteiniq.io/api/v1/jobs/job_123/cancel"Successful cancellation returns:
{
"object": "job_cancellation",
"refunded": true,
"job": {
"id": "job_123",
"object": "job",
"status": "CANCELLED"
}
}If the job is already running or terminal, the API returns conflict with details.current_status. A workflow-owned job also returns conflict, without that detail; cancel its workflow run instead.