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.

Bash
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.

JSON
{
  "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.

JSON
{
  "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.

Bash
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.

Job resource

Single-job endpoints return this resource shape:

JSON
{
  "id": "job_123",
  "object": "job",
  "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.

Bash
curl -s \
  -H "Authorization: Bearer $PROTEINIQ_API_KEY" \
  "https://proteiniq.io/api/v1/jobs?limit=20"

List jobs supports:

  • limit: Integer from 1 to 100, defaults to 20
  • starting_after: Cursor returned as next_cursor from a previous page

The response is ordered by newest job first.

JSON
{
  "object": "list",
  "data": [],
  "has_more": false,
  "next_cursor": null
}

Get job status

GET /api/v1/jobs/{jobId}/status returns the current job resource.

Bash
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.

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.

Bash
curl -s -X POST \
  -H "Authorization: Bearer $PROTEINIQ_API_KEY" \
  "https://proteiniq.io/api/v1/jobs/job_123/cancel"

Successful cancellation returns:

JSON
{
  "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.