# Demeter for agents: a Free testnet port, no account

This page is for coding agents. It says how to get a working Cardano API
endpoint from Demeter without an account, and how to hand it to your person
afterwards.

## What you get

- A Free port on a Cardano testnet: `cardano-preprod` or `cardano-preview`.
  Mainnet and paid tiers need your person to claim the project first.
- No account, no credit card. You register, and Demeter creates a project
  that nobody owns yet.
- Up to 3 ports in that project.
- 7 days. If your person has not claimed the project by then, it expires
  with its ports and keys.
- Once claimed, Free for 30 days from registration; your person enrolls the
  project to keep it (see [Keeping Free](#keeping-free)).

## 1. Register

If `.env.demeter` already exists, you have registered before: reuse it and
skip to step 2. Registering again overwrites the file and starts a new
project.

Write the answer straight to a file. It holds secrets: never print it.

```sh
curl -sS -X POST https://console.demeter.run/api/agent/register \
  -H 'content-type: application/json' \
  -d '{"agent": {"name": "claude-code", "version": "2.1.0"}}' \
  -o .env.demeter
```

`agent.name` is required, at most 64 characters: say which agent you are.
`agent.version` and `agent.url` are optional, at most 256 characters each.
Your person sees them when they claim the project.

On success `.env.demeter` holds, in this order:

```
DEMETER_PROJECT_ID=<uuid>
DEMETER_PROJECT_NAMESPACE=<namespace>
DEMETER_API_KEY=dmtr_apikey1…
DEMETER_CLAIM_URL=https://console.demeter.run/claim/<token>
DEMETER_USER_CODE=XXXX-XXXX
DEMETER_EXPIRES_AT=<RFC 3339 UTC>
```

On a refusal the file holds a JSON error instead (see
[Errors](#errors)). This prints the error, and nothing on success:

```sh
grep -v '^DEMETER_' .env.demeter
```

Read the lines that are safe to show, and only those:

```sh
grep -E '^DEMETER_(CLAIM_URL|USER_CODE|EXPIRES_AT)=' .env.demeter
```

## 2. Create a port

Send the project's key and id, and append the answer to the same file:

```sh
set -a; . ./.env.demeter; set +a
curl -sS -X POST https://console.demeter.run/api/agent/ports \
  -H "dmtr-api-key: $DEMETER_API_KEY" \
  -H "project-id: $DEMETER_PROJECT_ID" \
  -H 'content-type: application/json' \
  -d '{"kind": "OgmiosPort", "network": "cardano-preprod"}' \
  >> .env.demeter
```

`network` is `cardano-preprod` or `cardano-preview`. `kind` is one of:

| `kind` | API | Docs |
|---|---|---|
| `BlockfrostPort` | Blockfrost-compatible REST | [/docs/blockfrost](https://demeter.run/docs/blockfrost) |
| `KupoPort` | Kupo, UTxOs by address or pattern | [/docs/kupo](https://demeter.run/docs/kupo) |
| `OgmiosPort` | Ogmios, JSON-RPC over WebSocket | [/docs/ogmios](https://demeter.run/docs/ogmios) |
| `UtxoRpcPort` | UTxO RPC, gRPC | [/docs/utxorpc](https://demeter.run/docs/utxorpc) |
| `TrpPort` | TRP, for tx3 transactions | [/docs/trp](https://demeter.run/docs/trp) |

Other kinds with a testnet option work the same way; an unknown `kind` is
refused with a message that lists every kind. The port gets the Free tier
and the default settings for its kind and network.

The answer adds these lines, where `<KIND>` is the kind upper-cased without
`PORT` (`OGMIOS`, `UTXORPC`) and `<NET>` is `PREPROD` or `PREVIEW`:

```
DEMETER_<KIND>_<NET>_PORT_ID=<resource id>
DEMETER_<KIND>_<NET>_URL=<endpoint>
DEMETER_<KIND>_<NET>_KEY=<the port's API key>
```

A port with more than one endpoint adds `_URL_2`, `_URL_3`. For Blockfrost,
Kupo and Ogmios one of them carries the key as the first host label, for
clients that cannot set a header. A `DbSyncPort` is the exception: its
`_URL` is a PostgreSQL connection string and its `_KEY` is
`username:password`, not an API key.

On a refusal the JSON error is appended instead. Print it the same way, then
drop it so the file loads again:

```sh
grep -v '^DEMETER_' .env.demeter
grep '^DEMETER_' .env.demeter > .env.demeter.tmp && mv .env.demeter.tmp .env.demeter
```

Run it again for another kind or network, up to 3 ports.

## Keys

- Never print `.env.demeter`, and never `cat`, `echo` or log a key from it.
  The `grep` lines above are the only parts you show.
- Load it into the environment instead: `set -a; . ./.env.demeter; set +a`.
- Keep it out of version control: add `.env.demeter` to `.gitignore`.
- Call a port with its own key, `DEMETER_<KIND>_<NET>_KEY`, in the
  `dmtr-api-key` header (a DbSync port takes it as its database login
  instead). `DEMETER_API_KEY` is the project's key: it creates
  ports, it does not call them. The [docs](https://demeter.run/docs) show
  each port's calls with the key in `$DEMETER_API_KEY`; set that to the
  port's key when you follow them.

## The claim

The project is yours to use, but it belongs to nobody until your person
claims it. Tell them, in your own words, to do one of:

- open `DEMETER_CLAIM_URL`;
- go to `https://console.demeter.run/activate` and enter `DEMETER_USER_CODE`.

They sign in or sign up, name the project, and it is theirs. Tell them when
it expires (`DEMETER_EXPIRES_AT`). Do not send them `.env.demeter`.

After the claim nothing in `.env.demeter` changes: the same URLs and keys
keep working, and your person can delete `DEMETER_API_KEY` in the Console.
The project stops expiring, and mainnet and the paid tiers become available
to it from the Console.

## Keeping Free

The project stays on Free for 30 days from registration. To keep it on Free
after that, your person enrolls the project in the free-tier program at
`https://demeter.run/free-tier`, or moves a port to a paid tier in the
Console. Otherwise they get a notice, and the ports are suspended 14 days
later. The project and its keys are kept, so enrolling or upgrading brings
the ports back. Tell your person this when you hand them the claim.

## Errors

Every refusal is JSON and carries no secret:

```json
{"error": "<code>", "message": "<one sentence>", "retry_after_seconds": 86400, "docs": "https://demeter.run/auth.md"}
```

`retry_after_seconds` is there only when waiting helps.

| HTTP | `error` | When | What to do |
|---|---|---|---|
| 400 | `invalid_request` | missing or oversized agent name, unknown kind | Fix the request; the message says what is wrong, and lists the kinds for an unknown one. |
| 401 | `unauthenticated` | bad API key on `/ports` | Load `.env.demeter` again and send `DEMETER_API_KEY` and `DEMETER_PROJECT_ID` as above. Do not register again for this. |
| 403 | `pre_claim_limit` | a mainnet network, a tier other than `0`, or a port kind with no preprod/preview option, on a provisional project | Use `cardano-preprod` or `cardano-preview` and leave the tier out. For mainnet, ask your person to claim the project and create the port in the Console. |
| 429 | `ip_cap` | 3 registrations from this client IP in the trailing 24 hours | Reuse the project you already registered. Otherwise wait `retry_after_seconds`. |
| 429 | `port_cap` | the provisional project already has 3 ports | Use the ports you have, or ask your person to claim the project. |
| 503 | `global_cap` | 200 unclaimed provisional projects are live | Try again later, or ask your person to sign up at `https://console.demeter.run` and create the port there. |
| 503 | `registration_disabled` | the kill switch is off | Same as `global_cap`: try later, or have your person sign up. |
| 410 | `expired` | the provisional project expired | Register again (step 1) and create the ports again. |

Any other failure is a `502` with `upstream`: try again later.

## Expiry

A project nobody claimed expires 7 days after registration
(`DEMETER_EXPIRES_AT`). Its ports stop answering, its keys stop working, and
`/api/agent/ports` answers `410 expired`. Register again to start over, and
tell your person sooner this time.
