# Ptarmigan-1 > Predicts which compounds engage a protein target, from sequence alone (no > structure). Screen a compound library against a human protein and get every > compound ranked by calibrated engagement probability. Your user created an API key for you at https://ptarmigan.app/api-keys. Send it on every request as `Authorization: Bearer `. The key acts as their account: it sees their libraries and spends their prepaid balance. Send a descriptive `User-Agent: acme-screen/1.0` on REST and MCP requests too. A Cloudflare 403 with `error code: 1010` means Cloudflare blocked the request before it reached this API, often because the User-Agent was missing or was an HTTP-library default such as `python-httpx`. Set your own User-Agent and retry. If you have no key, ask your user to create one at https://ptarmigan.app/api-keys (it is shown once) and to add it to your client as the bearer credential. In claude.ai that is a custom connector with the URL https://ptarmigan.app/api/v1/mcp, Authentication "No sign-in", and a request header `authorization` whose value is `Bearer `; step by step: https://ptarmigan.app/docs/agents. ## Conventions - Timestamps are ISO 8601 in UTC, with the offset written out (`+00:00`). - Libraries and screens finish in the background. Check the first time after about 5 seconds, then every few seconds. - A library's `source` is `catalog` (provided to every account) or `upload` (your user's). Names are not unique; refer to a library by its `id`. - A compound's `compound_id` is the `id` from the library's CSV (for example `CHEMBL5095102`), or its position (1, 2, 3, ...) when the CSV had no `id` column. It is not a UUID. - Spending is capped only by you: estimate a screen and compare its cost with your user's budget before you queue it. ## MCP Connect an MCP client (Streamable HTTP) to https://ptarmigan.app/api/v1/mcp with the same bearer header. Its tools: list_proteins, list_compound_collections, get_compound_collection, get_balance, estimate_screen, create_screen, list_screens, get_screen, cancel_screen, get_screen_hits, get_screen_download, get_screen_compound_detail Uploading and deleting a library, and download links for several screens at once, are REST only (below). MCP pages are smaller by default than REST's: get_screen_hits returns 25 and list_proteins 20 unless you pass `limit`. List responses contain `data` and `next_after`. Pass `next_after` as `after` with the same filters until it is null; `limit` controls the page size. Before calling create_screen, confirm the cost from estimate_screen with your user: it is the only call that spends their balance, and it cannot be undone once the screen starts. Pass create_screen an `Idempotency-Key` argument (a fresh UUID per screen) and the same key when retrying: a retry then returns the first call's screen instead of queuing and charging another. If you passed none and lost the result, call list_screens with the protein_id and collection_id (every status, newest first) and look for one created since your call before calling create_screen again. ## REST Reference: https://ptarmigan.app/docs/api (OpenAPI: https://ptarmigan.app/docs/api/openapi.json). Guides for people: https://ptarmigan.app/docs (curl: https://ptarmigan.app/docs/rest, errors: https://ptarmigan.app/docs/errors). Every error the API returns is JSON `{"code", "message"}`; branch on `code`. A 502 or 504 may instead come from a proxy as an HTML page: retry it like a 503. List responses contain `data` and `next_after`: pass `next_after` as `after` with the same filters until it is null; `limit` sets the page size. 1. Find a target: GET /api/v1/proteins?search=EGFR. Only the human proteins listed there can be screened; there is no way to add one. Each protein has a `coverage` object: `known` (false: the scored range wasn't recorded; the protein can still be screened), `sequence_length`, `residue_start` and `residue_end` (the scored range, 1-indexed), `is_truncated` (true: only that range was scored), `training_max_residues`, and `ood_reasons` (`exceeds_training_length`: the protein is longer than the model saw in training). See "Reading the numbers" before trusting a ranking. 2. Find or upload a library: GET /api/v1/compound-collections, or POST /api/v1/compound-collections (multipart `file` = CSV with a `smiles` column, optional `name`/`id`; 413 when larger than the plan allows). The library is named after the file (without its extension) unless you send a `name` form field. Poll GET /api/v1/compound-collections/{id} until `status` leaves `embedding`: `ready` can be screened; `failed` (see `error`) means upload it again; `outdated` must be prepared again at https://ptarmigan.app/libraries before it can be screened. Check `skipped_rows`: rows skipped as unreadable, too long, missing an id, or a mixture with no clear parent compound. Compounds are standardized (salts removed, canonical SMILES); `input_smiles` keeps yours. DELETE /api/v1/compound-collections/{id} removes an upload. 3. Price it: POST /api/v1/screens/estimate {"protein_id", "collection_id"}. Cost is one token per compound; `sufficient` says whether the balance covers it. GET /api/v1/balance shows the balance. 409 `library_not_ready` means the library is still embedding. 4. Confirm the cost with your user, then queue it: POST /api/v1/screens with the same body -> 202 and a screen id. 402 means insufficient funds; ask your user to add funds at https://ptarmigan.app/billing. Send an `Idempotency-Key` header (a fresh UUID per screen) and send the same key when retrying: a key already used returns that screen (200, `Idempotent-Replayed: true`) instead of queuing and charging another. Without a key every POST is a new, charged screen. If you sent none and lost the response, list GET /api/v1/screens?protein_id=...&collection_id=... (every status, newest first) and look for one created since you posted: it may already be `running` or `completed`. Post again only if there is none. 5. Poll GET /api/v1/screens/{id} until status is `completed`, `failed` (not charged) or `cancelled`. Cancel with POST /api/v1/screens/{id}/cancel while it is still `queued`. 6. Read results (available until `results_expire_at`, at no cost): GET /api/v1/screens/{id}/hits?limit=100 for the top of the ranking (cursor: pass `next_after` as `after`); GET /api/v1/screens/{id}/download for a short-lived link to every compound as Parquet (or `?format=csv`); POST /api/v1/screens/downloads {"screen_ids": [...]} for one link per screen, up to 100 (REST only); GET /api/v1/screens/{id}/compounds/{compound_id} for one compound's detail, and with `?include_residues=true` its `residue_engagement` (one number per residue; `null` without the flag). Reference proteome (off-target) predictions are in the web app only: no REST route or MCP tool returns them. Take `compound_id` from the hits. It may contain `/`, which goes in the path as is; percent-encode `#`, `?`, `%` and spaces. ## Reading the numbers `engagement` is a calibrated probability (0-1) that the compound engages the protein, and the ranking key. It is computed the same way for every target and library (one calibration, and pooling that does not depend on protein length), so values from screens with the same `model_version` are on one scale and can be compared across screens and targets. Use it first to rank, and don't treat a fixed cutoff such as 0.5 as engaged versus not engaged. `rank` is a hit's position in that ranking, from 1. `score` is the raw cosine at the peak residue, uncalibrated. `predicted_residue` is the highest-scoring residue (1-indexed) and `predicted_residue_letter` its amino acid. Predictions prioritize compounds for experimental testing; they are not measurements. Check the target's `coverage` before trusting a ranking, and tell your user when it applies: `known: false` means the scored range wasn't recorded, so truncation can't be checked; `is_truncated: true` means only `residue_start` to `residue_end` of its `sequence_length` residues were scored; `exceeds_training_length` in `ood_reasons` means the protein is longer than the model's training proteins (`training_max_residues`), so treat its predictions with more caution.