# Scopes

> Narrow an API key to exactly what an integration needs. One scope table governs both the REST API and the MCP server.

Source: https://dev.ody.co/get-started/scopes

Scopes narrow a key **within** its creator's access. They never grant anything the member who created the key couldn't do themselves. The same scope gates a REST endpoint and its MCP twin, so a read-only key produces a genuinely read-only AI assistant.

## Scope reference

| Scope | Label in the app | REST endpoints | MCP tools |
|---|---|---|---|
| `contacts:read` | Read contacts | `GET /contacts`, `GET /contacts/:id` | `list_contacts` |
| `contacts:write` | Manage contacts | `POST /contacts`, `PATCH /contacts/:id`, `DELETE /contacts/:id` | `create_contact`, `update_contact`, `delete_contact` |
| `conversations:read` | Read conversations | `GET /conversations`, `GET /conversations/:id`, `GET /calls`, `GET /messages/search` | `list_conversations`, `get_conversation`, `search_messages`, `workspace_summary`, `wait_for_message` |
| `conversations:write` | Update conversations | `PATCH /conversations/:id` | `set_conversation_status` |
| `messages:send` | Send messages | `POST /messages` | `send_message` |
| `numbers:read` | Read numbers | `GET /numbers` | `list_numbers` |
| `numbers:manage` | Buy numbers & 10DLC | `GET /numbers/available`, `POST /numbers`, `/messaging*` | `search_numbers`, `buy_number`, `get_messaging_status`, `submit_10dlc_registration` |
| `calls:place` | Place AI calls | `POST /calls` | `place_call` |
| `agent:manage` | Manage AI agent | `GET/PATCH /agent`, `POST /agent/publish`, `POST /numbers/:e164/connect`, `…/disconnect` | `get_agent`, `configure_agent`, `connect_agent_to_number` |
| `webhooks:manage` | Manage webhooks | `/webhooks*`, `GET /webhook-events` | — |

All paths are relative to `https://api.ody.co/v1/api`. `GET /openapi.json` needs no key at all.

## How scopes behave

- **Full access by default.** A key created with every scope selected — or before scopes existed — has full access, including scopes added later. The app shows it as **Full access**.
- **Narrowed keys get exactly their list.** An explicit subset grants only those scopes. A key with zero scopes can't be created.
- **REST:** an out-of-scope call returns `403 permission_denied` naming the missing scope.
- **MCP:** tools the key can't use are **not advertised** in `tools/list`, so the assistant never sees them. If a client calls one anyway, it gets a tool error naming the scope. See [MCP safety](https://dev.ody.co/mcp/safety).

## Recommended scope sets

| Integration | Scopes |
|---|---|
| Read-only AI assistant | `contacts:read`, `conversations:read`, `numbers:read` |
| Inbox assistant that can reply | the above + `messages:send`, `conversations:write` |
| CRM contact sync | `contacts:read`, `contacts:write` |
| Order-notification sender | `messages:send` |
| Event-driven backend | `webhooks:manage` + whatever it reads |
| Agent Phone provisioning (numbers → 10DLC → Astra → calls) | `numbers:read`, `numbers:manage`, `agent:manage`, `calls:place` |
| Analytics export | `conversations:read` |
