# auth.md — getting a Trickest credential

You are an agent that wants to call Trickest. The resource servers are
`https://trickest.io/api` (REST) and `https://api.trickest.io/mcp` (hosted MCP,
Streamable HTTP). There is **no authorization server**: `https://trickest.io`
issues no OAuth tokens to third parties.

Read this whole file before you try anything — the short version is that a human
creates one long-lived API token in the Trickest dashboard and gives it to you.

**Not supported, so do not attempt it:**

- No OAuth 2.0 authorization server, no `/.well-known/oauth-authorization-server`,
  no `agent_auth` metadata block, and therefore no `skill`, `identity_endpoint`,
  `claim_endpoint` or `events_endpoint`.
- No dynamic client registration (RFC 7591). There is no endpoint that mints a
  client for you, and none that lets you register on a user's behalf.
- No `identity_assertion` and no id-jag / JWT-bearer exchange (RFC 7523). You
  cannot trade an assertion from your own IdP for a Trickest token.
- No RFC 7009 token revocation endpoint and no RFC 8935 event stream.

## Discover

Two ways in, both honest about what they return.

**1. Probe and read the challenge.** An unauthenticated MCP call answers 401 with
a `WWW-Authenticate` header naming both accepted schemes:

```http
POST /mcp HTTP/1.1
Host: api.trickest.io
Content-Type: application/json
Accept: application/json, text/event-stream

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"your-agent","version":"1.0"}}}
```

```http
HTTP/2 401
content-type: application/json
www-authenticate: Token realm="trickest-mcp", Bearer realm="trickest-mcp"

{"jsonrpc":"2.0","error":{"code":-32000,"message":"Missing authorization token"},"id":null}
```

Note what is *not* there: the challenge carries a `realm`, not a
`resource_metadata` parameter, so you cannot chase a PRM URL out of the header.
Fetch it directly instead.

**2. Read the protected-resource metadata.** `https://trickest.com/.well-known/oauth-protected-resource`
describes the resource and how a credential is presented to it. It names no
authorization server, which is the machine-readable form of "there is nothing to
run an OAuth flow against here."

The REST surface is described by `https://trickest.com/openapi.json` (OpenAPI 3.0; the
security scheme is an HTTP authorization header).

## Pick a method

There is one method. The decision tree has one branch:

| Situation | What to do |
|---|---|
| A human is available | Ask them to create an API token in the Trickest dashboard and give it to you. |
| No human is available | Stop and ask for one. There is no self-service, no anonymous, and no deferred-ownership registration. |

The credential is a **static, long-lived API token** scoped to the user who
created it and to that user's vault (their workspace). It is not scoped down per
agent, it carries the full permissions of that user, and it does not expire on a
schedule — treat it like a password.

## Register

Registration is a human action in the dashboard. Instruct your user:

1. Sign in at `https://trickest.io` (free Community account: `https://trickest.io/signup`).
2. Open **Settings → Developer → API Token** — `https://trickest.io/settings/api-keys`.
3. Create a token and copy it. It is shown so it can be copied; it is not
   retrievable later by API.
4. Hand it to you through whatever secret channel your runtime uses, and set it
   as `TRICKEST_TOKEN` in the environment rather than pasting it into a prompt
   or a committed file.

There is no request you can make that performs this step. Do not attempt to POST
to a registration endpoint; none exists.

## Claim

Nothing to claim. The claim ceremony in the auth.md draft — ceremony materials,
a verification link for the user, polling a claim grant — has no counterpart
here, because the token is already the user's own credential when it reaches
you. The equivalent of "user consent" happened when the human created the token
and gave it to you.

If your framework requires a claim step, treat the human handoff as complete on
first successful call: `trickest auth whoami` (or `GET /api/users/me/token`)
round-trips the token to the server and tells you which identity and vault you
are acting as.

## Use the credential

Same token on both surfaces. The scheme is `Token`, not `Bearer` — with one
exception: JWT-shaped credentials (they contain `.`) use `Bearer`. Dashboard
API tokens are not JWTs, so use `Token`.

**MCP:**

```json
{
  "mcpServers": {
    "trickest": {
      "url": "https://api.trickest.io/mcp",
      "headers": { "Authorization": "Token <your-api-token>" }
    }
  }
}
```

**REST:**

```bash
curl -sS https://trickest.io/api/users/me/token \
  -H "Authorization: Token $TRICKEST_TOKEN"
```

**CLI / SDK** (the environment variable always wins over stored credentials):

```bash
export TRICKEST_TOKEN=<your-api-token>
trickest auth status
# {"authenticated":true,"source":"TRICKEST_TOKEN env var","email":"you@org.com","vault":"your-vault"}
```

There is no refresh token and no refresh call: the same string keeps working
until a human deletes it.

## Errors

| Where | Response | What it means | What to do |
|---|---|---|---|
| MCP, no header | 401 + `WWW-Authenticate: Token realm="trickest-mcp", Bearer realm="trickest-mcp"`, JSON-RPC `{"error":{"code":-32000,"message":"Missing authorization token"}}` | You sent no credential. | Ask the human for a token. Do not retry unauthenticated. |
| MCP, bad header | 401, same challenge | The token is malformed, revoked, or for another environment. | Ask for a fresh token; retrying is pointless. |
| REST, no header | 401 `{"code":"UNAUTHORIZED","message":"Not authenticated"}` | No credential. | As above. |
| REST, bad token | 401 `{"code":"UNAUTHORIZED","message":"Invalid token.","traceId":"…"}` | Rejected credential. Quote `traceId` in support requests. | As above. |
| REST | 403 | Authenticated, but this user or plan cannot do it. | A different token will not help; the action needs different permissions or a different plan. See https://trickest.com/pricing.md. |
| REST | 429 | Rate limited. | Back off, honour `Retry-After` when present. |
| CLI | exit code `2`, `ERR_AUTH` in the `{code, message, hint}` envelope | Missing/invalid token — or a separately gated command (e.g. `billing`). | If `trickest space ls` works and one command does not, the token is fine and that command is gated. |
| SDK | `AuthError` (401), `ForbiddenError` (403), `RateLimitError` (429) | Branch on the class, not on the message string. | — |

A 401 here is never a signal to start an authorization flow, because there is no
flow to start. It is a signal to ask a human.

## Revocation

Revocation is also a human action, in the same place the token was created:
**Settings → Developer → API Token** (`https://trickest.io/settings/api-keys`).
Deleting or regenerating the token invalidates it immediately; the next call
returns the 401 above. There is no programmatic revocation endpoint, no
introspection endpoint, and no event stream that will notify you when it
happens — you find out from a 401.

If you lose control of a token, or the human ends your access, tell them to
regenerate it. Locally, `trickest auth logout` clears only stored credentials on
that machine (it does not unset `TRICKEST_TOKEN` in the shell, and it does not
revoke anything server-side).

---

_Verified against production on 2026-09-03. Machine-readable companions:
https://trickest.com/.well-known/oauth-protected-resource, https://trickest.com/openapi.json,
https://trickest.com/llms.txt. Full operating manual: https://trickest.com/for-agents.md._
