# 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": "/claim/…"
}
```

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

**Store it.** Provisioning is rate-limited per source and the key is valid for 30 days —
requesting a second one because you did not keep the first is the main way agents get a
`429` here.

## 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.
- 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.
