Jobs
Jobs are asynchronous tool runs tied to one workspace and one API key workspace scope.
A job represents one execution of a ProteinIQ tool with a specific input payload 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 to validate a payload and estimate credits without creating a job.
curl -s -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": ">seq\nMKT...",
"source": { "type": "text" }
}
]
},
"settings": {}
}'Quote responses include credit estimates, available credits, blocking errors, current limits, and billing mode.
{
"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 to create a job. The request must be JSON and must include tool, name, input, and optional settings and billing.
{
"tool": "esmfold",
"name": "Lysozyme ESMFold run",
"input": {
"inputs": [
{
"id": "seqs_1",
"slotId": "protein",
"kind": "protein",
"format": "fasta",
"content": ">seq\nMKT...",
"source": { "type": "text" }
}
]
},
"settings": {},
"billing": {
"max_reserved_credits": 100
}
}Use Idempotency-Key or X-Idempotency-Key when retrying job submission. The same key is scoped to the workspace.
curl -s -X POST \
-H "Authorization: Bearer $PROTEINIQ_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: fold-lysozyme-001" \
"https://proteiniq.io/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": {}
}'Submit returns a job resource with status code 202 for new jobs. Replayed idempotent submissions return the stored response status.
Strict 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": "Lysozyme ESMFold run",
"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 -s \
-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
GET /api/v1/jobs/{jobId}/status returns the current job resource.
curl -s \
-H "Authorization: Bearer $PROTEINIQ_API_KEY" \
"https://proteiniq.io/api/v1/jobs/job_123/status"Poll until the job reaches a terminal status. The API currently treats COMPLETED, FAILED, TIMEOUT, CANCELLED, and BUDGET_EXCEEDED as terminal statuses for client workflows.
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 -s \
-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 -s -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
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 GET /api/v1/jobs/{jobId}/events instead of tight polling.
Only call GET /api/v1/results/{jobId} after the status endpoint or events stream reports a result-ready terminal state. 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 -s -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.