# Create a key

URL: https://docs.fxapis.com/api-reference/api-keys/create-a-key

> Create a key. Needs an admin key (fx_admin_…), made by the workspace owner in the console — or a signed-in owner or admin.

`POST https://api.fxapis.com/v1/api-keys`

**Needs an admin key** (`fx_admin_…`), made by the workspace owner in the console — or a signed-in owner or admin. Ordinary API keys are refused with `ADMIN_KEY_REQUIRED`.

Returns the key in `key`, **once**. We cannot show a key again after creation, or recover it. A lost key is replaced, not recovered.

`admin`, `keys:manage`, `members:manage` and `billing:manage` cannot be granted here, not even by an admin key. Issue narrow keys: an integration that only reads positions should hold `trading:read` and nothing else.

A console session confirms it's you first (`403 STEP_UP_REQUIRED`) only when the key can move money or accounts — `trading:execute`, `trading:reduce` or `accounts:write`. A read-only key, or one that only manages webhooks, is created without one.

Authentication: `Authorization: Bearer <API key>`.

#### Request body (application/json, required)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `name` | string | yes | Example: `"Production trading server"` |
| `scopes` | "accounts:read" \| "accounts:write" \| "trading:read" \| "trading:reduce" \| "trading:execute" \| "history:read" \| "billing:read" \| "webhooks:write"[] | yes | `admin` and `keys:manage` cannot be granted to a key through the API. |
| `environment` | "live" \| "test" | no |  |
| `expiresAt` | string (date-time) | no | Optional. Must be in the future. |
| `limits` | null \| object | no | What the key may open, on top of its scopes. Each field is its own limit; leave one out for no limit. Checked on every opening order the key places — through the API, a multi-account order, a TradingView hook it created, or MCP. Closing, cancelling and moving stops are never limited. A refused order answers `403 KEY_LIMIT_EXCEEDED`. |
| `limits.maxVolume` | null \| string | no | Lots per opening order. Example: `"1.00"` |
| `limits.symbols` | null \| string[] | no | Broker symbols or instruments the key may open, compared without case. Example: `["EURUSD","XAUUSD"]` |
| `limits.accountIds` | null \| string (uuid)[] | no | The accounts the key may open on. |
| `limits.maxOpeningOrdersPerDay` | null \| integer | no | Opening orders per UTC day. Each account in a multi-account order counts as one. Example: `100` |

#### Responses

- `201`
- `400`
- `401`
- `403`
- `404`
- `409`

Failures return `{ "error": { "code", "message" } }`; every code is listed at https://docs.fxapis.com/errors.

#### 201 response fields

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `data` | object | no |  |
| `data.id` | string (uuid) | no |  |
| `data.name` | string | no |  |
| `data.publicId` | string | no | The key's public half. Safe to log and to show. |
| `data.hint` | string | no | The key with its secret elided, e.g. `fx_live_7f3a9c21_…`, for telling keys apart. |
| `data.environment` | "live" \| "test" | no |  |
| `data.scopes` | string[] | no |  |
| `data.lastUsedAt` | null \| string (date-time) | no | To the minute. Null if it has never been used. |
| `data.expiresAt` | null \| string (date-time) | no |  |
| `data.revokedAt` | null \| string (date-time) | no |  |
| `data.createdAt` | string (date-time) | no |  |
| `data.limits` | null \| object | no | What the key may open, on top of its scopes. Each field is its own limit; leave one out for no limit. Checked on every opening order the key places — through the API, a multi-account order, a TradingView hook it created, or MCP. Closing, cancelling and moving stops are never limited. A refused order answers `403 KEY_LIMIT_EXCEEDED`. |
| `data.limits.maxVolume` | null \| string | no | Lots per opening order. Example: `"1.00"` |
| `data.limits.symbols` | null \| string[] | no | Broker symbols or instruments the key may open, compared without case. Example: `["EURUSD","XAUUSD"]` |
| `data.limits.accountIds` | null \| string (uuid)[] | no | The accounts the key may open on. |
| `data.limits.maxOpeningOrdersPerDay` | null \| integer | no | Opening orders per UTC day. Each account in a multi-account order counts as one. Example: `100` |
| `data.createdBy` | null \| object | no | Who made the key in the console. Removing or suspending that person revokes it. Null for a key made with an admin key (see `createdByAdminKey`), or before keys recorded their maker. |
| `data.createdBy.id` | string (uuid) | no |  |
| `data.createdBy.name` | string | no |  |
| `data.createdByAdminKey` | null \| object | no | The admin key that made this key, when one did. Revoking that admin key does not revoke this one. |
| `data.createdByAdminKey.id` | string (uuid) | no |  |
| `data.createdByAdminKey.name` | string | no |  |
| `data.key` | string | no | The whole key. Shown here and never again — store it now. We cannot show it again after creation. |