API reference

Paper Crane exposes a thin REST surface over its tRPC core. Every endpoint below is generated from a procedure in packages/trpc/src/routers — the same source of truth used by the dashboard.

Authentication

All requests require a bearer token. Mint one from the dashboard or via the keys.create procedure. The plaintext key is shown once — store it in your secret manager, then reference it via the Authorization header.

Authorization: Bearer pk_live_8f2a...d09e
Content-Type:  application/json

Keys can be rotated (24h grace) or revoked immediately. See keys.rotate and keys.revoke.

POST/v1/keyskeys.create

Mint an API key. Plaintext is returned once and never stored — copy it immediately. Subsequent calls only expose the prefix.

request
{
  "name": "ci-prod-key",
  "scopes": ["jobs:write", "designs:read"],
  "expiresAt": "2027-01-01T00:00:00Z"
}
response (200)
{
  "id": "key_01HZ...",
  "prefix": "pk_live_8f2a",
  "plaintext": "pk_live_8f2a...d09e",
  "createdAt": "2026-04-29T18:22:11Z"
}
curl
curl -X POST https://api.papercrane.bio/v1/keys \
  -H "Authorization: Bearer $PAPERCRANE_SESSION" \
  -H "Content-Type: application/json" \
  -d '{"name":"ci-prod-key","scopes":["jobs:write"]}'
POST/v1/jobsjobs.submit

Submit a design job. Pass a toolId (single tool or pipeline composite like rfdiffusion+proteinmpnn+boltz2) and a DesignRequest. Returns immediately with a queued job id.

request
{
  "toolId": "bindcraft",
  "request": {
    "target": { "pdbId": "2LGV", "chain": "A" },
    "hotspots": ["A45", "A48", "A52"],
    "numDesigns": 100,
    "seeds": [1, 2, 3, 4, 5]
  },
  "webhookUrl": "https://example.com/hooks/papercrane",
  "idempotencyKey": "campaign-2026-04-29-001"
}
response (200)
{
  "id": "job_01HZ...",
  "toolId": "bindcraft",
  "status": "queued",
  "submittedAt": "2026-04-29T18:23:04Z",
  "numDesigns": 100,
  "completedDesigns": 0,
  "etaSeconds": 230
}
curl
curl -X POST https://api.papercrane.bio/v1/jobs \
  -H "Authorization: Bearer $PAPERCRANE_API_KEY" \
  -H "Content-Type: application/json" \
  -d @job.json
GET/v1/jobs/{id}jobs.get

Poll job state. Status transitions: queued → preparing → running → (succeeded | partial | failed | cancelled).

response (200)
{
  "id": "job_01HZ...",
  "toolId": "bindcraft",
  "status": "running",
  "submittedAt": "2026-04-29T18:23:04Z",
  "startedAt":   "2026-04-29T18:23:41Z",
  "numDesigns": 100,
  "completedDesigns": 37
}
curl
curl https://api.papercrane.bio/v1/jobs/job_01HZ... \
  -H "Authorization: Bearer $PAPERCRANE_API_KEY"
GET/v1/designs/{id}designs.get

Fetch a single design. Response includes metrics and a presigned URL pointing to the structure artifact (CIF) in object storage.

response (200)
{
  "id": "des_01HZ...",
  "jobId": "job_01HZ...",
  "seed": 17,
  "rank": 3,
  "metrics": {
    "ipsae": 0.81,
    "iptm":  0.74,
    "plddt": 88.2
  },
  "structureUrl": "https://artifacts.papercrane.bio/...sig=...",
  "structureFormat": "cif"
}
curl
curl https://api.papercrane.bio/v1/designs/des_01HZ... \
  -H "Authorization: Bearer $PAPERCRANE_API_KEY"
GET/v1/jobs/{jobId}/designsdesigns.listByJob

List every design produced by a job, ordered by rank. Use this to drive ranking tables and bulk-export pipelines client-side (download structures via each design's presigned URL).

response (200)
{
  "designs": [
    { "id": "des_01HZ...a", "seed": 1,  "rank": 1, "metrics": { "ipsae": 0.83 } },
    { "id": "des_01HZ...b", "seed": 17, "rank": 2, "metrics": { "ipsae": 0.81 } }
  ],
  "nextCursor": null
}
curl
curl https://api.papercrane.bio/v1/jobs/job_01HZ.../designs \
  -H "Authorization: Bearer $PAPERCRANE_API_KEY"
POST/v1/projectsprojects.create

Create a project. Projects are the org-scoped container for jobs, targets, and designs.

request
{
  "name": "RBX1 binder campaign",
  "description": "BindCraft sweep against 2LGV chain A"
}
response (200)
{
  "id": "prj_01HZ...",
  "name": "RBX1 binder campaign",
  "createdAt": "2026-04-29T18:24:55Z"
}
curl
curl -X POST https://api.papercrane.bio/v1/projects \
  -H "Authorization: Bearer $PAPERCRANE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"RBX1 binder campaign"}'

Errors follow tRPC conventions: HTTP 400 (BAD_REQUEST), 401 (UNAUTHORIZED), 403 (FORBIDDEN), 404 (NOT_FOUND), 429 (TOO_MANY_REQUESTS), 500 (INTERNAL_SERVER_ERROR). Bodies are JSON with { error: { code, message } }.

Looking for the CLI? /docs/cli. Or get oriented at /docs/getting-started.