# Introducing the API and Python SDK

> A public API and a Python SDK are now available, giving scripts, notebooks, and services the same tools, jobs, files, and results that power the web app.

The ProteinIQ API and an official [Python SDK](/docs/api/python-sdk) are now available. They give scripts, notebooks, services, and lab systems the same tools, jobs, files, and results that power the web app, so you can run analysis from code instead of clicking through the UI.

Most real work is repetitive. You fold a batch of sequences, screen a ligand library, or rerun the same pipeline every time a new target arrives. The API turns that into a few lines: submit a job, wait for it, pull the results. Everything lives under `https://proteiniq.io/api/v1` and authenticates with a workspace API key.

## Submit a job, wait, and download results

Jobs are asynchronous. Submission returns a job resource immediately, then you poll status or stream events until the job reaches a terminal state, then you fetch the result.
```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": {}
  }'
```
The response carries an `id` and a `PENDING` status. Poll `GET /api/v1/jobs/{id}/status` until the job is `COMPLETED`, or stream `GET /api/v1/jobs/{id}/events` for live updates without repeated polling. Once it is result-ready, `GET /api/v1/results/{id}` returns structured output plus a file manifest with signed download URLs.

Job input always goes inside `input.inputs[]`, with one entry per required slot. Read [Tools](/docs/api/tools) to inspect a tool's exact input contract, or browse the same catalog in [Tools](/app/tools).

## The Python SDK

The SDK wraps the same API in a small client built on `httpx`. It needs Python 3.10 or newer and reads `PROTEINIQ_API_KEY` from the environment.
```bash
pip install proteiniq
```

```python
from proteiniq import ProteinIQ

client = ProteinIQ()

job = client.jobs.submit(
    tool="esmfold",
    name="Lysozyme ESMFold run",
    input={
        "inputs": [
            {
                "id": "seqs_1",
                "slotId": "protein",
                "kind": "protein",
                "format": "fasta",
                "content": ">lysozyme\nKVFGRCELAAAMKRHGLDNYRGYSLGNWVCAAKFESNFNTQATNRNTDGSTDYGILQINSR",
                "source": {"type": "text"},
            }
        ]
    },
    idempotency_key="fold-lysozyme-001",
)

job = client.jobs.wait(job.id)
paths = client.results.download_files(job.id, "./outputs")
print(paths)
```
`client.jobs.wait()` handles status polling and respects the `Retry-After` header, and `download_files` saves the signed result files to a directory you choose. API error envelopes map to typed exceptions like `RateLimitError`, `ValidationError`, and `ProteinIQError`, so you can branch on `exc.code` instead of parsing strings.

## Quote costs before you run

Paid jobs need enough workspace credits before they start. Use a quote to validate input and estimate credits without creating a job.
```python
quote = client.jobs.quote(tool="esmfold", input=esmfold_input, settings={})
print(quote.estimated_credits, quote.available_credits)
```
The quote also reports blocking errors and current concurrency limits, which makes it a good preflight check before submitting a large batch.

## Reuse files instead of re-uploading

Upload an input once and reference it across jobs by id, so you do not resend the same structure or sequence in every request. Uploads use the same validation as the web app, with a 50 MB limit per file.
```python
uploaded = client.files.upload("./receptor.pdb", tags=["receptor"])

job = client.jobs.submit(
    tool="esmfold",
    name="Fold saved structure",
    input={"inputs": [{
        "id": "protein_1",
        "slotId": "protein",
        "kind": "protein",
        "format": "pdb",
        "source": uploaded.input_reference,
    }]},
)
```
ProteinIQ resolves the saved file server-side before the job starts, so compute receives normal input content. See [Files](/docs/api/files) for origins, scopes, and deletion behavior.

## Keys, scopes, and rate limits

API keys are created on the [API keys](/app/settings/api) page and require a workspace on a paid plan. A key is bound to one workspace, uses the bearer format `pq_live_<keyId>_<secret>`, and is shown in full only once, so store it as a secret and keep it out of browser code and git history.

Keys carry scopes for reading and writing jobs and files: `jobs:read`, `jobs:write`, `files:read`, and `files:write`. The public API allows 120 requests per 60 seconds per key, and status and result reads are throttled to once every 5 seconds per job. Poll with exponential backoff, starting at 5 seconds and capping at 30, and use the events stream when you need faster updates. When a retry is risky, reuse the same `Idempotency-Key` to avoid creating duplicate jobs.

## Start building

Create a key on the [API keys](/app/settings/api) page, then run a full submit, status, result, and download loop with the [Quickstart](/docs/api/quickstart). The [API overview](/docs/api) covers the full surface, the [Python SDK](/docs/api/python-sdk) reference covers the client, and `GET /api/v1/openapi.json` gives a machine-readable contract for generating typed clients. API access is included in every paid plan; see [Pricing](/pricing) for the current comparison.
