skills/tamarind/references/api_reference.md
Spec: the OpenAPI spec at https://app.tamarind.bio/openapi.yaml (3.0, auth ApiKeyAuth) covers the 8 core job endpoints (/submit-job, /submit-batch, /jobs, /result, /upload/{filename}, /files, /delete-job, /delete-file) — fetch it for those exact shapes. It does not include the discovery/management endpoints (/tools, /usage-statistics, /submit-pipeline, /run-pipeline, /stop-job) — for those, use this file + the live MCP getAvailableTools/getJobSchema/getJobs. This file also adds the behaviors no spec spells out (response-shape-by-query, two-step result download, batch aggregation polling, REST-vs-MCP field differences).
Base URL: https://app.tamarind.bio/api/
Authentication: x-api-key: <YOUR_KEY> header on every request.
Interactive docs: app.tamarind.bio/api-docs · markdown docs at docs.tamarind.bio
There is no official Python SDK. Call the API with requests (Python) or curl. An MCP server (https://mcp.tamarind.bio/mcp, X-API-Key header) exposes the same operations with agent-friendly schemas.
| Method | Path | Purpose |
|---|---|---|
| GET | /tools | List available tools and their inline parameter schemas. Returns the full list (no server-side filtering — filter client-side). |
| POST | /submit-job | Submit one job. Body: jobName, type, settings (+ optional projectTag). |
| POST | /submit-batch | Submit many jobs of the same tool. See payload shapes below. |
| GET | /jobs | List/inspect jobs. Query: jobName, batch, limit, startKey, organization, includeSubjobs, jobEmail. |
| POST | /result | Get a presigned download URL for job results (two-step — see below). Body: jobName (+ optional fileName, pdbsOnly, jobEmail). |
| POST | /stop-job | Stop a running or queued job. Body: jobName. |
| DELETE | /delete-job | Delete a job and its data. Body: jobName. |
| PUT | /upload/{filename} | Upload a file (--data-binary; add ?folder= to file it). Or get a presigned URL via MCP uploadFile. |
| GET | /files | List your account's uploaded files as a flat array of filename strings. Query: folder, includeFolders=true. Does not enumerate a specific job's outputs — use MCP listJobFiles for that. |
| DELETE | /delete-file | Remove a file/folder. Query: filePath or folder. |
| POST | /submit-pipeline | Run a multi-step pipeline defined inline via stages[]. |
| POST | /run-pipeline | Run a pipeline saved in the UI. Body: pipelineName, initialInputs/inputs. |
| GET | /usage-statistics | Usage/billing. Query: statistic (weighted_hours/jobs), scope (user/org). |
Returns a JSON array. Each element:
{
"name": "alphafold",
"displayName": "AlphaFold",
"description": "Accurate and quick protein structure prediction ...",
"github": "https://github.com/...",
"paper": "https://...",
"settings": [ { "name": "sequence", "type": "sequence", "required": true, "description": "..." }, ... ]
}
In each settings param, only name and required are guaranteed; type, default, description, options are present only when applicable (about 60% of params carry type). Read them with param.get("type"), not param["type"].
settings is the tool's inline parameter schema — read it directly, no separate schema endpoint over REST. The REST list is not filtered by query params; filter client-side on name/displayName/description. (The MCP getAvailableTools wraps the list as {"totalTools", "tools":[...]} and adds categories/tags per tool plus server-side search/category/tag filtering.)
{
"jobName": "my-protein-analysis",
"type": "alphafold",
"settings": { "sequence": "MKT...", "numRecycles": 3 },
"projectTag": "proj_xxxxxxxx"
}
jobName — unique, ^[a-zA-Z0-9_-]+$, 1-100 chars.type — a tool name from /tools. The list changes often; never hardcode.settings — tool-specific; match the schema from /tools (or MCP getJobSchema).projectTag — optional proj_... ProjectId to file the job under a project.Response (200): a confirmation string like myJobName submitted to queue.
Two payload shapes appear in the official docs — the Python form uses parallel arrays; the curl form uses a jobs[] array of objects with a tool key. The parallel-array form matches the MCP submitBatch and is the recommended one:
{
"batchName": "egfr-screen",
"type": "alphafold",
"jobNames": ["seq1", "seq2"],
"settings": [{ "sequence": "..." }, { "sequence": "..." }],
"maxRuntimeSeconds": 3600,
"weightedHoursBudget": 100
}
curl-form alternative (same endpoint): { "tool": "<type>", "batchName": ..., "jobs": [{ "jobName": ..., "settings": {...} }, ...] }.
jobNames and settings are parallel arrays, same length, 1-100 items, all using the same tool.maxRuntimeSeconds — optional per-job timeout. weightedHoursBudget — optional budget cap.submitBatch schema exposes maxRuntimeSeconds + weightedHoursBudget. Some accounts/tools may accept an optional gpuType (seen in the docs UI), but it isn't in openapi.yaml or the MCP schema — treat it as unverified and confirm with support before relying on it.Response shape depends on the query:
jobName, or ?batch=/?organization=) → { "jobs": [...], "startKey": "...", "statuses": {...} }.?jobName=<name>) → the job row object directly (no jobs wrapper). Don't index ["jobs"][0] on this response.Each job row includes JobName, Type, JobStatus, Created, Started, Completed, Settings (JSON string), Score (JSON string, tool metrics), WeightedHours. Use startKey for pagination past the limit (default 1000). Only top-level jobs return by default; add includeSubjobs=true for batch subjobs.
Batch parent rows have Type: "batch" and carry batchStatus. Fetched by name (?jobName=<batchName>), a complete batch parent also includes resultUrl (presigned download). batchStatus transitions: Running → Aggregating → Complete (or AggregationFailed, with AggregationError). Poll the parent's batchStatus, not subjob JobStatus — subjobs go Complete before the aggregated output is ready.
Discriminate batch vs single by Type == "batch" (or presence of batchStatus), not by statuses. A by-name response can carry a statuses tally even for a single (non-batch) job, so statuses presence is not a reliable batch signal.
POST returns a presigned URL as a bare string (not JSON). Fetch that URL with a second GET to download the results zip:
url = requests.post(f"{BASE}/result", headers=H, json={"jobName": "myJob"}).text.strip('"')
open("myJob.zip", "wb").write(requests.get(url).content)
Optional body fields: fileName (one file instead of the zip), pdbsOnly: true (PDB outputs only), jobEmail (a teammate's job, if permitted).
| Code | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad request — invalid parameters/settings |
| 401 | Unauthorized — invalid/missing x-api-key |
| 403 | Budget exceeded (org/team) |
| 429 | Rate limited |
| 404 | Not found (e.g. unknown job) |
| 500 | Server error |
The REST and MCP schemas expose different fields. The REST /tools entry
gives a trimmed per-param view — {name, type, required, default, description, options}.
The advanced gating keys exclude and conditionals appear only in MCP
getJobSchema, not in REST /tools (restrictOrgs is no longer returned by
either surface — see below). So don't try to hand-derive what to strip from REST
schema keys — they aren't there. The reliable guard on
both surfaces is validateJob (MCP): it runs /submit-job's exact validation
without submitting and returns the first error.
validateJob's normalized output.
normalized is informational (defaults filled in, sometimes platform-managed
fields). Submit the same clean settings you validated, not the normalized echo.submit_method, monomer_msa, msa are
set by the platform. Never pass them.restrictOrgs — org-gated parameters. getJobSchema no longer returns this
key (it's stripped server-side): a parameter your account isn't authorized for is
dropped from the schema entirely, and any param you do see is one you may set. So
you won't encounter restrictOrgs in a response — don't look for it.conditionals (MCP schema only) — a field only applies when another field
has a given value (e.g. pairMode applies only when useMSA is true). Don't
send conditioned fields when their condition isn't met.exclude: [...] (MCP schema only) — marks a field as UI/pipeline-only for a
surface. Treat it as advisory; validateJob is the authority on what a given
submission accepts.required: true — must be present. Some tools require more than sequence
(e.g. boltz requires inputFormat). Run validateJob to get the first
missing/invalid field before submitting.target.pdb) — the platform scopes it to your account, so do NOT email-prefix
it. The {email}/{filename} form is the underlying S3 key, and passing it makes
submit-job 400 with "The following files have not been uploaded: <email>/<file>".
To reference a prior job's output, use JobName/path/to/file.ext. Confirm the
exact registered name with getFiles / GET /files (a flat list of bare names).TAMARIND_API_KEY (env or .env); never hardcode or commit it.x-api-key) and the MCP server (X-API-Key).organization=true/jobEmail, to your org if permitted).