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

# Partner API reference

> Every partner endpoint, with its auth, request, responses, errors, and quotas.

The partner API lives under `/api/v1/partner` on your Revyl API base URL, such as
`https://backend.revyl.ai`. To get a partner API key, email
[support@revyl.ai](mailto:support@revyl.ai).

## Requests

- **Auth:** `Authorization: Bearer <partner API key>` on every request. The key identifies your
  platform; the account comes from the path or body. Keep the key on your servers.
- **Accounts:** `{external_account_id}` is your ID for a customer account: 1 to 128 letters,
  digits, `_`, or `-`.
- **Request IDs:** send a UUID in `X-Request-Id`, and Revyl returns it on the response for support.
- **Caching:** responses that carry a credential or a dashboard link have
  `Cache-Control: no-store`. Don't log or cache them.

## Errors

Every error the API defines has one shape. Branch on `error.code`, not on the message:

```json
{
  "error": {
    "code": "not_connected",
    "message": "There's no connection for this external account."
  }
}
```

An `already_connected` error also carries `connection`. An unexpected `500` has no `error.code`;
retry it with backoff.

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `invalid_request` | The request failed validation. |
| 400 | `authorization_pending`, `slow_down`, `access_denied`, `expired_token` | Approval polling outcomes. See [Wait for approval](/partners/connect#wait-for-approval). |
| 401 | `invalid_partner_credential` | The partner key is missing, invalid, or revoked. |
| 403 | `partner_not_allowed` | Your integration, or this account, isn't enabled in this environment. |
| 403 | `not_supported_for_connection` | This operation isn't available for this connection. |
| 404 | `not_connected` | There's no connection for this account. |
| 409 | `already_connected` | The account is already connected. |
| 409 | `connection_in_progress` | Another request for this account is running. Retry after `Retry-After`. |
| 409 | `needs_support` | Revyl found an inconsistent state. Contact [support@revyl.ai](mailto:support@revyl.ai). |
| 429 | `rate_limited` | Too many requests this minute. Retry after `Retry-After`. |
| 429 | `daily_limit_reached` | A daily cap is spent. It resets at 00:00 UTC. |
| 503 | `temporarily_unavailable` | Retry after `Retry-After`, with bounded backoff. |

## Quotas

Requests count against two per-minute buckets: one for your platform and one for each account.
Responses report each bucket that was checked:

| Header | Meaning |
| --- | --- |
| `X-RateLimit-Limit-Partner`, `X-RateLimit-Limit-Account` | Requests allowed per UTC minute. |
| `X-RateLimit-Remaining-Partner`, `X-RateLimit-Remaining-Account` | Requests left this minute. |
| `X-RateLimit-Reset-Partner`, `X-RateLimit-Reset-Account` | When the window resets, in Unix seconds. |

A missing header doesn't mean unlimited. The values are snapshots, so pace your requests and
honor `Retry-After` on a `429`. Two daily caps also reset at 00:00 UTC: new workspaces Revyl
creates for your platform, and invitations per connection.

## Endpoints

### Connect an account

`POST /api/v1/partner/connections`

Body: `external_account_id`, `external_account_name`, `organization_name`, `owner_email`, and
optional `mode` (`auto` or `link_existing`). Names are 1 to 200 characters.

| Status | Body |
| --- | --- |
| `201` | `connection` (`state: "connected"`) and `credential` |
| `202` | `connection` (`state: "awaiting_approval"`) and `approval` |

Errors include `403 partner_not_allowed`, `409 already_connected`, `409 connection_in_progress`,
`409 needs_support`, and `429 daily_limit_reached`.

### Collect an approved credential

`POST /api/v1/partner/connections/{external_account_id}/approval/token`

No body. `200` returns `connection` and `credential`, once. Until then it answers one of the
`400` polling codes.

### Get a connection

`GET /api/v1/partner/connections/{external_account_id}`

`200` returns the connection:

```json
{
  "external_account_id": "acct_123",
  "state": "connected",
  "organization": { "id": "<organization UUID>", "name": "Example Workspace" },
  "origin": "provisioned"
}
```

`state` is `connected`, `awaiting_approval`, or `not_connected`. `organization` is present only
while connected. `origin` is `provisioned` when Revyl created the workspace and `existing` when
an admin linked one.

### Rotate the connection credential

`POST /api/v1/partner/connections/{external_account_id}/credentials`

No body. `201` returns `credential`: `type: "api_key"`, `secret`, and `expires_at`. The
replaced credential keeps working for a short grace period; older ones and session credentials stop
at once. Errors include `404 not_connected` and
`409 connection_in_progress`.

### Mint a session credential

`POST /api/v1/partner/connections/{external_account_id}/session-credentials`

Optional body: `{ "ttl_seconds": 7200 }`, from 60 to 14,400. `201` returns `credential_id` and
`credential`: `type: "session_token"`, `secret`, and `expires_at`. Errors include
`404 not_connected`.

### Revoke a session credential

`DELETE /api/v1/partner/connections/{external_account_id}/session-credentials/{credential_id}`

`204`. An unknown ID also answers `204`. It can also answer `409 connection_in_progress`.

### Invite a teammate

`POST /api/v1/partner/connections/{external_account_id}/invitations`

Body: `{ "email": "sam@example.com" }`. `202` returns `{ "status": "sent" }`, and the person
joins as a Member. Revyl also answers `202` when it already invited that address this UTC day,
without sending again. Errors include `400 invalid_request` when Revyl rejects the invitation,
`403 not_supported_for_connection` for a linked organization, `404 not_connected`,
`409 connection_in_progress`, and `429 daily_limit_reached`.

### Request a dashboard link

`POST /api/v1/partner/connections/{external_account_id}/dashboard-links`

No body. `200` returns `url`, `requires_login: true`, and `expires_at: null`. See
[Dashboard links](/integrations/partner-dashboard).

### Read usage

`GET /api/v1/partner/connections/{external_account_id}/usage?from=<ISO time>&to=<ISO time>`

`from` and `to` are ISO 8601 times with a time zone, such as `2026-10-01T00:00:00Z`.

`200` returns `period` and `usage`: one row per feature, with `seconds` for device and build
time and `millicents` for AI usage, for work started through the account's connections. The
period is `[from, to)` and at most 93 days. Errors include `400 invalid_request` and
`404 not_connected` for an account that was never connected.

### Disconnect an account

`DELETE /api/v1/partner/connections/{external_account_id}`

`204`. Revokes the connection's credentials and session credentials and stops the live sessions
started with them. App embeds keep working until you revoke them with
`DELETE /api/v1/embeds/{embed_id}`. Safe to repeat. A `503` means something couldn't be stopped yet; retry to finish.
