# Tamarind Bio — agent authentication

> Machine-readable answer to "I am an AI agent, I have no account, how do I call this API?"
> Short answer: `POST /api/agent/provision` with an empty body.
> No authentication, no signup form, no email, no CAPTCHA, no card.

Served at `/auth.md`, the convention for telling agents how to register.

**Paths below are relative on purpose — resolve them against the host you fetched this
document from.** This file is served from every deployment: production, staging, and each
customer's dedicated host. A hardcoded `https://app.tamarind.bio` would send an agent that
found this on staging, or on a customer's own domain, to a different deployment — where the
credential it is about to mint does not exist, and where its jobs would not be visible to the
people who deployed it. The URLs in the provisioning RESPONSE are absolute and already
host-correct; prefer those once you have them.

## Identity types supported

| Type | Human needed? | What you get |
|---|---|---|
| `anonymous` | No | A working API key immediately, with trial limits. This is the one you want. |
| `claimed` | Once, one sign-in | The same key, upgraded to a full free account: 10 jobs/month, all 300+ tools. |
| `none` | No | The tool catalog at `/tools.json` needs no credential at all. |

## Get a key

```bash
curl -X POST "$TAMARIND_HOST/api/agent/provision"   # the host you fetched this file from
```

No headers, no body. Response `201`:

```json
{
  "apiKey": "…",
  "email": "agent-….@agents.tamarind.agent",
  "tier": "agent-trial",
  "expiresAt": "…",
  "limits": {
    "jobs": 10,
    "allowedTools": "all",
    "maxRuntimeSeconds": null,
    "expiresInDays": 30
  },
  "claimUrl": "https://<this host>/claim/…"
}
```

Send the key as `x-api-key` on every request afterwards.

**Keep the key for every job; give your user the link.**

- **`apiKey`: save it and reuse it for every job you run for this user.** You need this same
  key to check jobs, download results and run the next job: a new key is a separate account,
  its jobs do not appear under the first claim link, and requesting a second one is the main
  way agents get a `429` here. Save it outside the project (never a file that could be
  committed), keep it out of your output, and don't delete it when the first job finishes. It
  is valid for 30 days, and does not expire once your user claims it.
- **`claimUrl`: it is for your user.** Put it in the same reply where you tell them the job
  is submitted — don't wait for results. Until they open it they cannot see the job, and it
  cannot be sent again later.

## What the trial can and cannot do

This is a real Tamarind free account, not a restricted sandbox — **the full free tier**.
Every tool in `/tools.json` is available (alphafold, boltz, chai, protenix, rfdiffusion,
docking, the lot), you get **10 jobs a month**, and jobs run under the ordinary free-tier
runtime limits. Do not downgrade to a "simpler" tool or a smaller run than the one you were
asked for.

**One limit is on SIZE, not on which tool.** An unclaimed credential draws on a shared
compute allowance, and a handful of free-energy tools estimate more than that whole allowance
for a single default run — `rbfe` and `abfe` are the ones you are most likely to reach for.
Those submissions come back `403` with `estimatedWeightedHours` and `allowanceWeightedHours`
so you can see the gap, and **retrying will not help**: an empty pool refuses them exactly as
a busy one does. Either submit a smaller run (fewer edges, fewer windows) or hand over the
claim URL — a claimed account is not subject to this ceiling. Every other tool in the
catalogue fits comfortably.

The one thing that differs from a signed-up account: an unclaimed key **stops working after
30 days**. Claiming removes that.

`POST /api/validate-job` is free and does not consume a job. Use it to get the payload right
before you spend one.

**What is not available.** `POST /api/submit-job` and `POST /api/submit-batch` both work, and
they are the two that spend compute. `/api/submit-pipeline`, `/api/run-pipeline`,
`/api/virtual-screening` and `/api/deploy-model` return `403` for an unclaimed key — the
pipeline routes because they are not metered against any quota, and model-deploy because it
runs caller-supplied containers. Signing in lifts all four. `/api/validate-job`, `/api/jobs`,
`/api/result` and `/api/files` work normally.

A `403` naming the route, or a `429`, is this rule firing rather than a broken request. Do not
retry it with a different payload, and do not report the platform as unavailable — surface the
claim URL instead.

**One refusal looks different, and it is the one you are most likely to hit first.** `GET
/api/jobs` is served by the API gateway rather than by this app, so an unauthenticated call
there answers with a bare `{"message":"Forbidden"}` — no `provisionApiKey`, no link back to
this document. Every other endpoint sends you here when the key is missing; that one cannot.
It means exactly the same thing: **you have no key, or you did not send it as `x-api-key`.**
It does not mean the platform is down, and it is not a reason to stop — `POST
/api/agent/provision` still works. If you want a cheap liveness probe that does point you
here, use `POST /api/validate-job`.

## Give the human the claim URL

The response includes `claimUrl`. Surface it. **This is the most useful thing you can do with
it.** One sign-in there:

- **moves the jobs you ran into that person's account**, so they see them in their dashboard;
- removes the 30-day expiry, so the key keeps working;
- attaches a real identity to work that had none.

Nothing you have already written needs to change — after the claim the same key acts as that
user, so your next submission lands in their account too.

Unclaimed credentials stop working after 30 days.

If the task you were asked to do needs a tool the trial cannot run, the right move is to
say so and hand over the claim URL, not to give up on the platform. That is the entire
reason this endpoint exists.

## Before you write code

- Confirm the exact `type` string against `/tools.json` (no key required). `type` is
  case-sensitive and is **not** the display name — `AlphaFold` is a label, `alphafold` is
  the type. Inventing a type is the most common way agent-written Tamarind code fails.
- Dry-run with `POST /api/validate-job` before submitting. It costs no compute and returns
  the normalized payload or the specific bad field. For a batch, pass `settings` as an
  ARRAY (up to 1000 rows and ~4.5 MB per call — over the size cap the answer is a bare
  `413` with no JSON body) and read the per-row verdicts in `results` — one call, not one
  per job. Looping it per job is what gets a client rate-limited and then edge-blocked.
- Jobs take minutes. Submit, then poll `GET /api/jobs`; do not block.
- Full REST reference: [/llms-full.txt](/llms-full.txt). Task-shaped
  setup guide: [/SKILL.md](/SKILL.md).

## Rate limits and refusals

| Status | Meaning | Do |
|---|---|---|
| `201` | Credential issued | Store it and proceed |
| `429` | Per-source or global hourly cap | Reuse the key you already have; otherwise wait for `retryAfterSeconds`, or ask the human to sign up |
| `503` | Provisioning temporarily unavailable | Retry after `retryAfterSeconds`, or ask the human to sign up |

A `429` is not a rejection of you specifically — the global cap is platform-wide. Do not
retry in a loop.

## Terms

Using a provisioned credential means accepting the
[Terms of Service](/terms-of-service). Automated access through the
documented API with a valid credential is permitted. Minting credentials in bulk, or
rotating them to evade the trial limits, is not — the limits exist so that this endpoint can
stay open to everyone else.
