> For the complete documentation index, see [llms.txt](https://docs.robotmoney.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.robotmoney.com/introduction/quickstart.md).

# Quickstart

Get an agent to its first decision in under ten minutes.

This guide takes an outside developer from signup to the first recorded decision from `POST /v1/authorizations`. RobotMoney does not move money in this flow. Your agent asks for a decision, then your own rail decides what to do with that answer.

## Values

Use staging while you build.

| Value          | Staging                                     | Production                      |
| -------------- | ------------------------------------------- | ------------------------------- |
| API base       | `https://staging-api.robotmoney.com/v1`     | `https://api.robotmoney.com/v1` |
| Auth issuer    | `https://staging-auth.robotmoney.com`       | `https://auth.brain.fi`         |
| Token endpoint | `https://staging-auth.robotmoney.com/token` | `https://auth.brain.fi/token`   |
| API resource   | `https://staging-api.robotmoney.com/`       | `https://api.brain.fi/`         |

The API also exposes the current agent key exchange metadata:

```bash
export BRAIN_API=https://staging-api.robotmoney.com/v1

curl -sS "$BRAIN_API/auth/agent-key-exchange"
```

Response:

```json
{
  "issuer": "https://staging-auth.robotmoney.com",
  "exchange": {
    "token_endpoint": "https://staging-auth.robotmoney.com/token",
    "resource": "https://staging-api.robotmoney.com/",
    "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
    "subject_token_type": "urn:brain:params:oauth:token-type:agent-api-key",
    "scope": "payment_intent:propose",
    "key_display": "once"
  }
}
```

Always take the `resource` value from this discovery response. It is the token audience for the API and is not necessarily the same host as the public API base.

## Curl

### 1. Sign Up

```bash
curl -sS -X POST "$BRAIN_API/signup" \
  -H 'content-type: application/json' \
  -d '{
    "email": "founder@example.com",
    "password": "correct horse battery staple"
  }'
```

Staging returns a `verification_token` in the response because staging is a sandbox deploy target. Production sends the token by email.

```json
{
  "tenant_id": "tnt_...",
  "user_id": "usr_...",
  "status": "pending",
  "verification_token": "verify_..."
}
```

### 2. Verify Email

```bash
curl -sS -X POST "$BRAIN_API/auth/verify-email" \
  -H 'content-type: application/json' \
  -d "{\"tenant_id\":\"$TENANT_ID\",\"token\":\"$VERIFICATION_TOKEN\"}"
```

### 3. Log In

```bash
curl -sS -X POST "$BRAIN_API/auth/login" \
  -H 'content-type: application/json' \
  -d '{
    "email": "founder@example.com",
    "password": "correct horse battery staple"
  }'
```

Save the returned owner JWT:

```bash
export OWNER_TOKEN=...
```

### 4. Register A Counterparty

The owner JWT is the setup credential. The agent key is only for proposing authorization decisions.

```bash
curl -sS -X POST "$BRAIN_API/ledger/counterparties" \
  -H "authorization: Bearer $OWNER_TOKEN" \
  -H 'content-type: application/json' \
  -d '{
    "name": "Acme Trading LLC",
    "type": "vendor",
    "contact_email": "billing@example.com",
    "country_code": "US"
  }'
```

Save the returned counterparty id:

```bash
export COUNTERPARTY_ID=cp_...
```

### 5. Create A Tenant Agent

```bash
curl -sS -X POST "$BRAIN_API/agents" \
  -H "authorization: Bearer $OWNER_TOKEN" \
  -H 'content-type: application/json' \
  -d '{
    "kind": "tenant",
    "name": "Ops Agent",
    "purpose": "Request payment authorization decisions",
    "autonomy_preset": "ask_always"
  }'
```

The response includes a one-time `api_key_once` value and an `exchange` object. The key is shown once. Store it in your secret manager.

```json
{
  "api_key_once": "brain_ak_test_...",
  "exchange": {
    "token_endpoint": "https://staging-auth.robotmoney.com/token",
    "resource": "https://staging-api.robotmoney.com/",
    "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
    "subject_token_type": "urn:brain:params:oauth:token-type:agent-api-key",
    "scope": "payment_intent:propose",
    "key_display": "once"
  }
}
```

### 6. Optional: Set Headless Limits

New self-serve tenants get defaults when they create their first tenant agent: `USD 100.00` per authorization and `USD 500.00` per day for `ach_outbound` and `card`.

Use the owner JWT to change the limit:

```bash
curl -sS -X PUT "$BRAIN_API/policy/headless-limits" \
  -H "authorization: Bearer $OWNER_TOKEN" \
  -H 'content-type: application/json' \
  -d '{
    "agent_id": "agent_...",
    "action_type": "ach_outbound",
    "currency": "USD",
    "per_authorization_limit": "100.00",
    "daily_window_limit": "500.00",
    "counterparty_allowlist_required": false
  }'
```

### 7. Exchange The Agent Key

```bash
curl -sS -X POST "$TOKEN_ENDPOINT" \
  -H 'content-type: application/x-www-form-urlencoded' \
  --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
  --data-urlencode "subject_token=$AGENT_KEY" \
  --data-urlencode "subject_token_type=urn:brain:params:oauth:token-type:agent-api-key" \
  --data-urlencode "resource=$API_RESOURCE" \
  --data-urlencode "scope=payment_intent:propose"
```

The access token expires after five minutes and has no refresh token. Exchange the agent key again when you need a fresh JWT.

### 8. Ask For A Decision

```bash
curl -sS -X POST "$BRAIN_API/authorizations" \
  -H "authorization: Bearer $AGENT_TOKEN" \
  -H "idempotency-key: auth-demo-001" \
  -H 'content-type: application/json' \
  -d '{
    "action_type": "ach_outbound",
    "execution": "external",
    "destination_counterparty_id": "'"$COUNTERPARTY_ID"'",
    "amount": "75.00",
    "currency": "USD",
    "reference": "inv-first-001"
  }'
```

`invoice_id` links a RobotMoney Ledger invoice and requires attached invoice evidence; for your own invoice number use `reference`.

Expected response for a registered counterparty under the default limit:

```json
{
  "authorization_id": "auth_...",
  "decision": "approved",
  "reason_code": "approved",
  "reason": "Approved by policy.",
  "execution": "external",
  "policy_id": "pol_...",
  "policy_version": 1
}
```

A registered counterparty above the limit returns `needs_review`. An unknown counterparty returns `blocked` with `counterparty_not_registered`.

### 9. Review Or Void Later

If a decision returns `needs_review`, a user or tenant service credential can approve or reject it:

```bash
curl -sS -X POST "$BRAIN_API/authorizations/$AUTHORIZATION_ID/approve" \
  -H "authorization: Bearer $OWNER_TOKEN" \
  -H 'content-type: application/json' \
  -d '{"reason":"Approved by finance"}'
```

If an approved external authorization was not paid, a user or tenant service credential can void it so it no longer counts against the policy window:

```bash
curl -sS -X POST "$BRAIN_API/authorizations/$AUTHORIZATION_ID/void" \
  -H "authorization: Bearer $OWNER_TOKEN"
```

## SDK

Install the SDK:

```bash
npm install @braindotfi/brain-core-sdk
```

Use `fetch` for setup routes that are not yet wrapped by SDK helpers, then use `agentApiKey` mode for authorization calls. In `agentApiKey` mode, the SDK reads `GET /v1/auth/agent-key-exchange`, exchanges the key, and refreshes the five-minute JWT before expiry.

```typescript
import { Brain } from "@braindotfi/brain-core-sdk";

const apiBase = "https://staging-api.robotmoney.com/v1";

async function post(path: string, body: unknown, token?: string) {
  const response = await fetch(`${apiBase}${path}`, {
    method: "POST",
    headers: {
      "content-type": "application/json",
      ...(token ? { authorization: `Bearer ${token}` } : {}),
    },
    body: JSON.stringify(body),
  });
  if (!response.ok) throw new Error(`${path} failed with ${response.status}`);
  return response.json();
}

const signup = await post("/signup", {
  email: "founder@example.com",
  password: "correct horse battery staple",
});

await post("/auth/verify-email", {
  tenant_id: signup.tenant_id,
  token: signup.verification_token,
});

const login = await post("/auth/login", {
  email: "founder@example.com",
  password: "correct horse battery staple",
});

const owner = new Brain({ token: login.token, environment: "staging" });

const counterparty = await owner.counterparties.create({
  name: "Acme Trading LLC",
  type: "vendor",
  contact_email: "billing@example.com",
  country_code: "US",
});

const agentResponse = await post(
  "/agents",
  {
    kind: "tenant",
    name: "Ops Agent",
    purpose: "Request payment authorization decisions",
    autonomy_preset: "ask_always",
  },
  login.token,
);

const agent = new Brain({
  agentApiKey: agentResponse.api_key_once,
  environment: "staging",
  agentScope: "payment_intent:propose",
});

await agent.http.ready();

const decision = await agent.authorizations.create({
  idempotencyKey: "auth-demo-001",
  action_type: "ach_outbound",
  execution: "external",
  destination_counterparty_id: counterparty.counterparty.id,
  amount: "75.00",
  currency: "USD",
  reference: "inv-first-001",
});

console.log(decision.decision);
```

## Usage Meter

`GET /v1/usage/authorizations?window=30d` is read-only. It returns recorded decision counts by tenant, agent, decision, and execution, plus the current catalog unit price and meter-only `amount_due`. It does not enforce billing and does not collect payment.
