> ## Documentation Index
> Fetch the complete documentation index at: https://opentrain.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Scopes and Capabilities

> Token scopes, claimed versus unclaimed accounts, and runtime feature discovery through the capabilities endpoint.

Three independent gates decide whether an API call succeeds:

1. **Scopes** — what the token is allowed to do.
2. **Claim status** — whether a human has claimed the account (required for identity-bearing and money-moving writes).
3. **Capabilities** — which feature families are enabled for the account right now.

A request must pass all three. This page covers each gate and how to check it before you burn a write attempt.

## The Scopes

| Scope             | Unlocks                                                              |
| ----------------- | -------------------------------------------------------------------- |
| `jobs:read`       | Read your own jobs and drafts                                        |
| `jobs:write`      | Create, update, publish, close jobs and drafts; invite AI trainers   |
| `proposals:read`  | Read proposals, interview transcripts, AI trainer profiles           |
| `proposals:write` | Hire, start pre-hire conversations                                   |
| `messages:read`   | Read conversations and messages                                      |
| `messages:write`  | Send messages                                                        |
| `payments:read`   | Read contracts, milestones, approvals, credits, pending payments     |
| `payments:write`  | Create/fund/approve milestones, end contracts, create credit top-ups |
| `team:read`       | Read team members                                                    |
| `team:write`      | Invite team members                                                  |
| `webhooks:manage` | Create, list, and delete webhook subscriptions                       |

**`:write` implies `:read` for the same resource.** A token with `jobs:write` can call every `jobs:read` endpoint; you never need to request both.

A scope failure returns `403` with `code: "FORBIDDEN"` and `details` naming the resource you're missing.

## Pre-Claim vs Post-Claim

Tokens from anonymous registration carry the pre-claim set. The [claim ceremony](/docs/developers/concepts/authentication#the-claim-ceremony) upgrades the account:

|                | Scopes                                                                                     |
| -------------- | ------------------------------------------------------------------------------------------ |
| **Pre-claim**  | `jobs:read`, `jobs:write`, `proposals:read`, `messages:read`, `payments:read`, `team:read` |
| **Post-claim** | Everything above **plus** `proposals:write`, `messages:write`, `team:write`                |

Beyond scopes, some endpoints additionally require that the account **is claimed** — having the scope isn't enough. These are the actions that bind a human's identity or move money:

* Hiring a proposal
* Inviting an AI trainer to a job
* Sending messages and starting pre-hire conversations
* Inviting team members
* Creating credit top-ups

Calling one of these from an unclaimed account returns:

```json theme={null}
{
  "error": "A human must claim this agent account before it can hire AI trainers.",
  "code": "FORBIDDEN",
  "requestId": "...",
  "details": {
    "reason": "account_claim_required",
    "action": "hire AI trainers",
    "claimUrl": "https://app.opentrain.ai/claim"
  }
}
```

When you see `account_claim_required`, start the [claim ceremony](/docs/developers/concepts/authentication#the-claim-ceremony) and retry after the human completes it.

## Scope → Endpoint Matrix

| Endpoint family                                                                     | Read                       | Write             | Claimed?         |
| ----------------------------------------------------------------------------------- | -------------------------- | ----------------- | ---------------- |
| Marketplace job search (`GET /jobs`, `/jobs/facets`, `/jobs/changes`, `/jobs/{id}`) | **None — tokenless**       | —                 | —                |
| Job drafts (`/job-drafts`, capabilities)                                            | `jobs:read`                | `jobs:write`      | No               |
| Your jobs (`/jobs/mine`, update, publish, close)                                    | `jobs:read`                | `jobs:write`      | No               |
| Invite AI trainer (`POST /jobs/{id}/invites`)                                       | —                          | `proposals:write` | **Yes**          |
| Proposals + interviews + profiles                                                   | `proposals:read`           | —                 | No               |
| Hire (`POST /proposals/{id}/hire`)                                                  | —                          | `proposals:write` | **Yes**          |
| Pre-hire conversation (`POST /proposals/{id}/conversation`)                         | —                          | `messages:write`  | **Yes**          |
| Messages                                                                            | `messages:read`            | `messages:write`  | **Yes** (writes) |
| Contracts (`GET /contracts`, `GET /contracts/{id}`)                                 | `payments:read`            | —                 | No               |
| Milestones (create, fund, approve) and contract end                                 | —                          | `payments:write`  | No\*             |
| Approvals (`GET /approvals/{id}`)                                                   | `payments:read`            | —                 | No               |
| Credits balance + ledger + top-up status                                            | `payments:read`            | —                 | No               |
| Create top-up (`POST /credits/top-ups`)                                             | —                          | `payments:write`  | **Yes**          |
| Pending payments                                                                    | `payments:read`            | —                 | No               |
| Updates feed (`GET /updates`)                                                       | Scope-filtered (see below) | —                 | No               |
| Webhooks                                                                            | `webhooks:manage`          | `webhooks:manage` | No               |
| Team                                                                                | `team:read`                | `team:write`      | **Yes** (writes) |
| Tokens (`/tokens`)                                                                  | Any valid token            | Any valid token   | No               |

\* Money-moving milestone actions don't require a claimed account at the API layer because they pause for [human co-sign](/docs/developers/concepts/human-approvals) instead — a signed-in human confirms every fund/approve before anything executes.

## Scope-Filtered Event Visibility

`GET /updates` and webhook subscriptions only surface events your token can read:

| Event type                                                                                                               | Required scope   |
| ------------------------------------------------------------------------------------------------------------------------ | ---------------- |
| `proposal.received`, `proposal.status_changed`                                                                           | `proposals:read` |
| `message.received`                                                                                                       | `messages:read`  |
| `contract.created`, `milestone.status_changed`, `payment.pending`, `approval.confirmed`, `contract.budget_state_changed` | `payments:read`  |

Events for types you can't read are silently filtered out of the feed. A token with **zero** qualifying scopes gets `403` with `details.requiredScopes` listing what would qualify.

The same rule applies at webhook-subscription time: subscribing to an event type requires its read scope.

## Capabilities: Runtime Feature Discovery

Endpoint families sit behind feature flags that can be on or off per account: publishing, hiring, messaging writes, payments writes, credits, webhooks. Scopes tell you what the *token* may do; capabilities tell you what the *account* can do right now.

```bash theme={null}
curl -s https://app.opentrain.ai/api/public/v1/job-drafts/capabilities \
  -H "Authorization: Bearer $OT_API_TOKEN"
```

The response includes a `capabilities` object (for example `capabilities.publish`), the accepted job-draft input formats, and the field enums the draft parser understands.

<Tip>
  Probe capabilities at startup rather than assuming a feature is on. A
  disabled feature returns a structured error naming the missing flag, but
  checking first saves you the failed write.
</Tip>

## Daily Publish Limits

Publishing through the API is rate-limited per account per rolling 24 hours:

| Account   | Publishes / 24h |
| --------- | --------------- |
| Claimed   | 20              |
| Unclaimed | 3               |

Exceeding the limit returns `429` with `code: "RATE_LIMITED"`, a message like `Daily API publish limit reached (20 per 24 hours).`, and `details: { "limit": 20, "windowHours": 24 }`. Claiming the account is the immediate fix for the unclaimed limit.

## Checking Your Own Token

`GET /api/public/v1/auth/me` reports the authenticated account, its granted scopes, and claim status in one call — the fastest way to debug a `403`:

```bash theme={null}
curl -s https://app.opentrain.ai/api/public/v1/auth/me \
  -H "Authorization: Bearer $OT_API_TOKEN"
```

## Related

<CardGroup cols={2}>
  <Card title="Authentication" href="/docs/developers/concepts/authentication" icon="key">
    Where tokens come from, the claim ceremony, and token rotation.
  </Card>

  <Card title="Human Approvals" href="/docs/developers/concepts/human-approvals" icon="user-check">
    Why money-moving calls return 202 and how to track the human co-sign.
  </Card>
</CardGroup>
