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

# MCP Authentication & Limits

> How the Numeral MCP server authenticates requests, isolates tenants, and reports errors.

## Authenticating

Every request to `https://mcp.numeralhq.com/mcp` carries your Numeral secret API key as a bearer token:

```
Authorization: Bearer sk_test_...
```

There is no OAuth flow and no separate MCP credential — the MCP server uses the same keys, lookup, and status checks as the [Platform REST API](/api-reference/v2026-03-01/introduction). Create and manage keys in your [developer dashboard](https://dashboard.numeralhq.com/developers) under the API Keys tab.

<Warning>
  Only **secret** keys (`sk_`) work. Publishable keys are rejected. A secret key grants full read access to your tax data, so keep it server-side — never ship it to a browser or commit it to a repository.
</Warning>

The server is stateless: each `POST` is authenticated independently and handled by a fresh server instance. Nothing is cached between requests, so revoking a key takes effect immediately.

## Test mode and live mode

The mode is derived from the key itself — you never pass a mode parameter:

| Key prefix | Mode       | Behavior                                              |
| ---------- | ---------- | ----------------------------------------------------- |
| `sk_test_` | Sandbox    | Reads sandbox data only. Nothing is filed or remitted |
| `sk_prod_` | Production | Reads live production data                            |

Merchant, transaction, and product data are fully segregated between modes. A test key cannot see production records and vice versa.

<Tip>
  Explore with an `sk_test_` key first. It is the safe way to see tool shapes and let an agent experiment. Switch to `sk_prod_` when you want answers about your real tax position.
</Tip>

One exception worth knowing: **exemption certificate tools are live mode only.** Calling them with a test key returns `live_mode_required`.

## Tenant isolation

Your key resolves to exactly one Numeral account, and every query the server runs is scoped to that account server-side. No tool accepts an account or client identifier, so there is no parameter an agent could set — accidentally or otherwise — that would reach another tenant's data.

The same applies to merchant lookups. `merchant_id` accepts either a Numeral ID (`mer_...`) or your own `reference_merchant_id`, but resolution is always constrained to merchants owned by your account in your current mode. An ID belonging to another account simply returns `merchant_not_found`.

## Rate limits

Requests are rate limited per account over a sliding window. The limit is sized for agent traffic — a single agent turn typically makes several requests (`initialize`, `tools/list`, then one or more `tools/call`) and that pattern fits comfortably within it.

Two details worth knowing:

* **MCP usage is metered separately from the REST API.** Heavy REST traffic does not consume your MCP quota, or vice versa.
* **The limit is keyed to your account, not to individual keys.** Minting additional API keys does not grant additional quota.

Exceeding the limit returns HTTP `429` with JSON-RPC error code `-32005`. Back off and retry; if you are hitting it consistently, look for a tool that answers the question in one call — `get_sales_summary` instead of paging through `list_transactions`, for example — or [contact us](mailto:support@numeralhq.com) about a higher limit.

## Two kinds of errors

The MCP server distinguishes between **protocol errors** (something is wrong with the request or the connection) and **tool errors** (the request was valid but the operation could not be completed). This matters when writing an agent: protocol errors need your code to react, while tool errors are meant for the model to read and correct.

### Protocol errors

Returned as JSON-RPC errors with a non-2xx HTTP status:

| HTTP  | JSON-RPC code | Meaning                                                  |
| ----- | ------------- | -------------------------------------------------------- |
| `401` | `-32001`      | Missing, malformed, unknown, revoked, or publishable key |
| `429` | `-32005`      | Rate limit exceeded                                      |
| `405` | `-32000`      | Method not allowed — only `POST` is supported on `/mcp`  |
| `500` | `-32603`      | Internal server error                                    |

The `message` field distinguishes the `401` cases: `Bearer token not provided.`, `Invalid API key format.`, `API key not found.`, `API key is not active.`, or `Public API keys are not supported. Use a secret key.`

```json theme={null}
{
  "jsonrpc": "2.0",
  "error": { "code": -32001, "message": "Bearer token not provided." },
  "id": null
}
```

<Note>
  `GET` and `DELETE` on `/mcp` return `405` by design. The server runs in stateless mode, so there are no SSE streams to resume and no sessions to terminate. For health checks use `GET /ping`, which returns `{"status":"ok"}`.
</Note>

### Tool errors

A tool that cannot complete returns a normal MCP tool result flagged with `isError`, whose content is:

```json theme={null}
{
  "error_code": "live_mode_required",
  "error_message": "Exemption certificates are live-mode only. Use your live (sk_prod_) API key."
}
```

`error_code` values are stable and machine-readable, and `error_message` is written to be actionable — an agent can usually read it and fix its own call. Unexpected exceptions are caught and returned this way too (as `internal_error`) rather than breaking the JSON-RPC connection, so one bad call never takes down an agent's session.

### Tool error codes

| Code                       | Meaning                                                                         |
| -------------------------- | ------------------------------------------------------------------------------- |
| `merchant_not_found`       | No merchant matches the given ID in this account and mode                       |
| `transaction_not_found`    | No transaction matches the given ID                                             |
| `filing_not_found`         | No filing matches the given ID                                                  |
| `invalid_filing_status`    | Unrecognized status filter. The message lists the valid statuses                |
| `certificate_not_found`    | No certificate matches the given ID                                             |
| `upload_not_found`         | No certificate upload matches the given ID                                      |
| `invalid_upload_id`        | The upload ID is malformed                                                      |
| `live_mode_required`       | The tool is live-mode only; retry with an `sk_prod_` key                        |
| `invalid_cursor`           | The pagination cursor is malformed or expired. Restart from the first page      |
| `invalid_range`            | The date range is invalid — for example `from` is after `to`                    |
| `product_not_found`        | No product matches the given `reference_product_id`                             |
| `invalid_product_category` | The category is not in the catalog. Use `list_tax_categories` for exact names   |
| `filing_not_approvable`    | The filing is not in `pending_client_approval` status                           |
| `filing_not_rejectable`    | The filing is not in `pending_client_approval` status                           |
| `buyer_identity_conflict`  | The supplied buyer reference ID conflicts with the customer that owns the email |
| `processing_start_failed`  | The certificate upload was accepted but processing could not be started         |
| `internal_error`           | An unexpected server-side error. Safe to retry                                  |

Six of these — `product_not_found`, `invalid_product_category`, `filing_not_approvable`, `filing_not_rejectable`, `buyer_identity_conflict`, and `processing_start_failed` — only occur on [write tools](/mcp/tools#write-tools), which are not enabled by default.

## Handling errors in an agent loop

Feed tool errors back to the model as tool results rather than throwing. The codes are designed for self-correction — a model that receives `invalid_product_category` will typically call `list_tax_categories` and retry with a valid value, and one that receives `live_mode_required` will explain the mode requirement instead of retrying blindly.

Reserve exceptions for protocol errors. A `401` means your key is wrong and no retry will help; a `429` means back off and retry with a delay.

The [demo application](/mcp/demo-app) implements this split in `app/api/chat/route.ts`.
