{
  "openapi": "3.1.0",
  "info": {
    "title": "Octolit Partner API",
    "version": "1.2",
    "description": "The partner API lets a broker's own CRM, website or backend work with the trading platform:\n\n* **Clients delta** - read every client of your company, page by change time (`updated_since` / `next_cursor`).\n* **Users** - create or update a client by **your** client id (`external_id`), read it back.\n* **Single sign-on** - a signed token from your system signs a client in (`/api/sso/...`), or your backend asks\n  for a one-time login link (`POST .../sso`). Sessions can be listed and ended.\n* **Trading accounts** - open a live or demo account under the brand's rules; read accounts, balance and\n  positions (read only - no order placement through this API).\n* **MT4 / MT5 links** - record which MT login on which server belongs to a user (link only).\n* **Trading control** - pause and resume trading for a user.\n* **Money posting** - book a deposit, withdrawal or credit on a user's account from your backend\n  (`POST .../transactions`, scope `balance:write` - off by default, granted by the Octolit CRM owner; an idempotency key\n  is required, every posting is audited).\n* **Webhooks** - signed HTTP calls to your endpoint for client, login, account, trade, price alert and money\n  events.\n\n**Authentication**: an API key from CRM Settings > API & Webhooks, sent as `X-Api-Key: <key>` or\n`Authorization: Bearer <key>`. Every key belongs to one company and carries scopes; a call without the scope\nanswers `403 insufficient_scope`. Keys may be bound to an IP allow-list and expire.\n\n**Rate limit**: the company's requests per minute (CRM setting); over it `429 rate_limited` with `Retry-After`.\n\n**Errors** always look like `{\"error\": {\"code\": \"...\", \"message\": \"...\", \"details\": {...}}}`.\n\n**Brand switches**: each brand can switch \"Single sign-on\", \"Partner API\" and \"Money posting\" off in CRM\nSettings; such calls answer `403 sso_disabled` / `403 brand_disabled`. Only money posting moves money, and\nonly through the Octolit CRM's own transaction service (the same path as a back-office \"Add transaction\").\n\n**Data protection** (CRM client page > Data management, not part of this API): a per-client data export (zip),\nerasure of personal data (refused with open positions, pending money or a balance; the client row stays as\n\"Erased client <id>\" and the same `external_id` then creates a new client), the sign-in history, a per-brand\nreduced-data mode (SSO-created clients get no name / e-mail; sign-in rows keep the IP's network only) and the\nretention settings. The plain-text guide (`/guide.txt`) has the details.\n"
  },
  "servers": [
    {
      "url": "https://{host}",
      "variables": {
        "host": {
          "default": "api.your-brand.com",
          "description": "The API host of your Octolit CRM (your account manager gives it to you)."
        }
      }
    }
  ],
  "tags": [
    {
      "name": "Account"
    },
    {
      "name": "Clients"
    },
    {
      "name": "Users"
    },
    {
      "name": "Single sign-on"
    },
    {
      "name": "Trading accounts"
    },
    {
      "name": "Money"
    },
    {
      "name": "MT links"
    },
    {
      "name": "Webhooks"
    }
  ],
  "security": [
    {
      "ApiKeyHeader": []
    },
    {
      "ApiKeyBearer": []
    }
  ],
  "paths": {
    "/api/external/v1/me": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "The key you are calling with",
        "operationId": "me",
        "responses": {
          "200": {
            "description": "The key, its scopes and the request cap.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "$ref": "#/components/schemas/ApiKeyInfo"
                    },
                    "company_id": {
                      "type": "integer"
                    },
                    "requests_per_minute": {
                      "type": "integer"
                    },
                    "server_time": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/external/v1/openapi.yaml": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "This document",
        "operationId": "openapi",
        "security": [],
        "responses": {
          "200": {
            "description": "The OpenAPI document (YAML).",
            "content": {
              "application/yaml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/external/v1/guide.txt": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "The broker integration guide (plain text)",
        "description": "A step-by-step guide for the broker's technical team: access, the partner API, single sign-on both ways,\nwebhooks (signature, retries, every event), data protection (export, erasure, sign-in history, reduced-data\nmode, retention) and a go-live checklist.\n",
        "operationId": "guide",
        "security": [],
        "responses": {
          "200": {
            "description": "The guide.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/api/external/v1/clients": {
      "get": {
        "tags": [
          "Clients"
        ],
        "summary": "Clients changed since a time (delta feed)",
        "description": "Clients of your company ordered by change time. Start with `updated_since`; afterwards send the last\n`next_cursor` (the cursor wins over `updated_since`) and keep reading while `has_more` is true. Rows changed\ninside the last few seconds (`as_of`) come with the next call.\n",
        "operationId": "listClients",
        "parameters": [
          {
            "name": "updated_since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Client"
                      }
                    },
                    "next_cursor": {
                      "type": "string"
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "as_of": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "per_page": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        }
      }
    },
    "/api/external/v1/clients/deleted": {
      "get": {
        "tags": [
          "Clients"
        ],
        "summary": "Clients deleted since a time",
        "operationId": "listDeletedClients",
        "parameters": [
          {
            "name": "updated_since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of `{ id, deleted_at }`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "deleted_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "type": "string"
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "as_of": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "per_page": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/external/v1/clients/{id}": {
      "get": {
        "tags": [
          "Clients"
        ],
        "summary": "One client by the Octolit CRM's id",
        "operationId": "getClient",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The client.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Client"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        }
      ],
      "get": {
        "tags": [
          "Users"
        ],
        "summary": "A user by your client id",
        "description": "Scope `clients:read`.",
        "operationId": "getUser",
        "responses": {
          "200": {
            "$ref": "#/components/responses/User"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "put": {
        "tags": [
          "Users"
        ],
        "summary": "Create or update a user by your client id",
        "description": "Scope `users:write`. Send any subset of the fields: a user that does not exist yet is created (201) in the\nbrand named by `brand_id` (default: the company's first active brand); an existing user is updated (200).\nA user never moves between brands (`409 brand_fixed`); an e-mail that belongs to another client of the\ncompany is refused (`409 email_taken`). Names, e-mail and phone are added, never removed.\n",
        "operationId": "upsertUser",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UserInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/User"
          },
          "201": {
            "$ref": "#/components/responses/User"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}/sso": {
      "post": {
        "tags": [
          "Single sign-on"
        ],
        "summary": "A one-time login link for a user (your backend asks)",
        "description": "Scope `sso:issue`. Answers a link to the brand's trader or client dashboard that signs the user in once.\nThe link lives `login_ttl_seconds` of the brand (default 60 s) and works a single time; the client's\ntwo-factor login (when switched on) still asks for the code. Open it in the user's browser (redirect) or\nhand the `token` to your page, which calls the app's `/login?token=` itself.\n",
        "operationId": "issueLogin",
        "parameters": [
          {
            "$ref": "#/components/parameters/ExternalId"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "target": {
                    "type": "string",
                    "enum": [
                      "trader",
                      "dashboard"
                    ],
                    "default": "trader"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The link.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LoginLink"
                }
              }
            }
          },
          "403": {
            "description": "`sso_disabled` (brand switch), `login_disabled` (the user may not sign in), `insufficient_scope`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`no_brand` or `no_app_domain` (the brand has no trader / dashboard domain yet).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}/sessions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        }
      ],
      "get": {
        "tags": [
          "Single sign-on"
        ],
        "summary": "The user's live sessions",
        "description": "Scope `sso:issue`. Newest first (at most 100).",
        "operationId": "listSessions",
        "responses": {
          "200": {
            "description": "The sessions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sessions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Session"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Single sign-on"
        ],
        "summary": "End every session of the user",
        "description": "Scope `sso:issue`. The user is signed out of the trader and the dashboard everywhere.",
        "operationId": "revokeSessions",
        "responses": {
          "200": {
            "description": "How many sessions ended.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ended": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}/sessions/{session_id}": {
      "delete": {
        "tags": [
          "Single sign-on"
        ],
        "summary": "End one session",
        "operationId": "revokeSession",
        "parameters": [
          {
            "$ref": "#/components/parameters/ExternalId"
          },
          {
            "name": "session_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ended.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ended": {
                      "type": "integer",
                      "enum": [
                        1
                      ]
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}/trading/disable": {
      "post": {
        "tags": [
          "Users"
        ],
        "summary": "Pause trading for a user",
        "description": "Scope `trading:control`. New positions, balance changes and transfers are refused for the user until\n`trading/enable`; open positions stay open and are still managed (stop loss, take profit, stop-out).\n",
        "operationId": "disableTrading",
        "parameters": [
          {
            "$ref": "#/components/parameters/ExternalId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/User"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}/trading/enable": {
      "post": {
        "tags": [
          "Users"
        ],
        "summary": "Resume trading for a user",
        "description": "Scope `trading:control`.",
        "operationId": "enableTrading",
        "parameters": [
          {
            "$ref": "#/components/parameters/ExternalId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/User"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}/accounts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        }
      ],
      "get": {
        "tags": [
          "Trading accounts"
        ],
        "summary": "The user's trading accounts with their balances",
        "description": "Scope `accounts:read`.",
        "operationId": "listAccounts",
        "responses": {
          "200": {
            "description": "The accounts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "accounts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TradingAccount"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Trading accounts"
        ],
        "summary": "Open a trading account (live or demo)",
        "description": "Scope `accounts:write`. The brand's own rules apply: the account type decides whether the account is demo\nor live (a demo type always gives a demo account; `demo: true` on the default live type gives a demo\naccount on live pricing), the brand's maximum number of live / demo accounts per client is enforced\n(`409 limit_reached`), the currency must be one the platform supports.\n",
        "operationId": "createAccount",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "account_type_id": {
                    "type": "integer",
                    "description": "An account type of the company (default = the company's default type)."
                  },
                  "currency": {
                    "type": "string",
                    "description": "3-letter code (USD",
                    "EUR": null,
                    "GBP": null,
                    "...) or its id; default USD.": null
                  },
                  "demo": {
                    "type": "boolean"
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 64
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new account (balance 0).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "account": {
                      "$ref": "#/components/schemas/TradingAccount"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}/balance": {
      "get": {
        "tags": [
          "Trading accounts"
        ],
        "summary": "The user's balance, equity and margin (all accounts)",
        "description": "Scope `accounts:read`. Values are the platform's live figures at `as_of`; MT accounts show the last figures the connector reported.",
        "operationId": "getBalance",
        "parameters": [
          {
            "$ref": "#/components/parameters/ExternalId"
          }
        ],
        "responses": {
          "200": {
            "description": "The balance.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "balance": {
                      "$ref": "#/components/schemas/Balance"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}/positions": {
      "get": {
        "tags": [
          "Trading accounts"
        ],
        "summary": "The user's positions",
        "description": "Scope `accounts:read`. Newest first; page with `before_id` = the last `id` you saw (`next_before_id`).",
        "operationId": "listPositions",
        "parameters": [
          {
            "$ref": "#/components/parameters/ExternalId"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "pending",
                "closed"
              ],
              "default": "open"
            }
          },
          {
            "name": "account_id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "before_id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "positions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Position"
                      }
                    },
                    "next_before_id": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    }
                  }
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}/transactions": {
      "post": {
        "tags": [
          "Money"
        ],
        "summary": "Book a deposit, withdrawal or credit on the user's account",
        "description": "Scope `balance:write` (off by default; only the Octolit CRM owner or an admin with the \"Edit transactions\" right\ncan put it on a key). The brand's \"Money posting\" switch must be on (`403 brand_disabled` otherwise).\n\nThe money is booked by the Octolit CRM's own transaction service - the same path a back-office \"Add transaction\"\ntakes (balance update, first-deposit date, client e-mails, postbacks; for an MT account the balance call\nto the MT server). `status: approved` (default) counts at once; `status: pending` waits for approval in\nthe Octolit CRM and then raises `deposit.status` / `withdrawal.status` like any other transaction.\n\n**Idempotency key** (required: `idempotency_key` in the body or the `Idempotency-Key` header, 1-128\ncharacters, unique per company). Repeat a call with the same key and body as often as you like: you get\nthe stored answer again (`200`, `replayed: true`) and nothing is booked twice. The same key with a\ndifferent body answers `409 idempotency_conflict`; a posting still being booked `409 in_progress`.\n\nA withdrawal over the account's free margin or a `credit_out` over its credit answers\n`409 insufficient_funds` and books nothing. Every posting leaves an audit row and a line in the client's\nCRM history naming the key.\n",
        "operationId": "postTransaction",
        "parameters": [
          {
            "$ref": "#/components/parameters/ExternalId"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "The idempotency key when it is not in the body."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MoneyPosting"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The same key and body again - the stored answer (`replayed` = true), nothing booked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MoneyPostingResult"
                }
              }
            }
          },
          "201": {
            "description": "Booked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MoneyPostingResult"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "`idempotency_conflict`, `in_progress` or `insufficient_funds` (details.available).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_request` (details.errors per field) or `money_rejected` (the Octolit CRM's transaction service refused).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}/mt_links": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        }
      ],
      "get": {
        "tags": [
          "MT links"
        ],
        "summary": "The MT4 / MT5 logins linked to the user",
        "description": "Scope `mt:link`.",
        "operationId": "listMtLinks",
        "responses": {
          "200": {
            "description": "The links.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "mt_links": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MtLink"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "MT links"
        ],
        "summary": "Link an existing MT4 / MT5 login to the user",
        "description": "Scope `mt:link`. Records that the login on the named server belongs to this user (the MT side itself is\nnot touched). One login per server belongs to one user (`409 already_linked`); at most 20 links per user.\n",
        "operationId": "linkMt",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "platform",
                  "server",
                  "login"
                ],
                "properties": {
                  "platform": {
                    "type": "string",
                    "enum": [
                      "mt4",
                      "mt5"
                    ]
                  },
                  "server": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "The MT server name as the broker calls it."
                  },
                  "login": {
                    "type": "string",
                    "maxLength": 64
                  },
                  "label": {
                    "type": "string",
                    "maxLength": 64
                  },
                  "trading_account_id": {
                    "type": "integer",
                    "description": "One of the user's trading accounts (optional)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The link.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "mt_link": {
                      "$ref": "#/components/schemas/MtLink"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}/mt_links/{link_id}": {
      "delete": {
        "tags": [
          "MT links"
        ],
        "summary": "Remove an MT link",
        "operationId": "unlinkMt",
        "parameters": [
          {
            "$ref": "#/components/parameters/ExternalId"
          },
          {
            "name": "link_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "unlinked": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/external/v1/quotes": {
      "get": {
        "tags": [
          "Market"
        ],
        "summary": "Current bid / ask of symbols (the same mirror the trading platform prices from)",
        "description": "Scope `accounts:read`. `?symbols=EURUSD,XAUUSD` or `?symbol_ids=1,2` (up to 200). `503 quotes_unavailable`\nwhile the price mirror is off or stale.\n",
        "operationId": "partnerQuotes",
        "parameters": [
          {
            "name": "symbols",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated symbol names."
          },
          {
            "name": "symbol_ids",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Comma-separated symbol ids."
          }
        ],
        "responses": {
          "200": {
            "description": "The quotes.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "quotes": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Quote"
                      }
                    },
                    "source": {
                      "type": "string",
                      "enum": [
                        "trading"
                      ]
                    },
                    "mirror_at": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "Epoch ms of the mirror's last update."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "503": {
            "description": "`quotes_unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/external/v1/bars": {
      "get": {
        "tags": [
          "Market"
        ],
        "summary": "Chart history (OHLCV bars) of a symbol",
        "description": "Scope `accounts:read`. The bars the platform's own chart draws, from the server's price history\n(every resolution the chart has). Pages backwards from `to`: when `complete` is false call again with\n`to=next_to`. `503 history_unavailable` (with `Retry-After`) while the history store is not loaded.\n",
        "operationId": "partnerBars",
        "parameters": [
          {
            "name": "symbol",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Symbol name (or symbol_id)."
          },
          {
            "name": "symbol_id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "resolution",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1m",
                "5m",
                "15m",
                "30m",
                "1h",
                "4h",
                "1D",
                "1W",
                "1M"
              ],
              "default": "1h"
            },
            "description": "Also accepted: 1, 5, 15, 30, 60, 240, D, W, M, 1d, 1w."
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Newest bar open time (epoch s",
            "epoch ms or ISO 8601); default now.": null
          },
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Oldest bar open time (same formats)."
          },
          {
            "name": "count",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 5000,
              "default": 500
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The bars, oldest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Bars"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "503": {
            "description": "`history_unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}/trending": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        }
      ],
      "get": {
        "tags": [
          "Market"
        ],
        "summary": "Trending instruments of the user's brand",
        "description": "Scope `accounts:read`. The instruments the brand's OWN clients traded most in the window (orders opened,\ndistinct clients, share, buy percentage) - never another brand's data, and only symbols at least\n`min_clients` clients traded. Per brand switch \"Trending instruments\" (default on): `403 trending_disabled`\nwhen off, `503 trending_unavailable` when the server has it off or cannot compute it right now.\n",
        "operationId": "partnerTrending",
        "parameters": [
          {
            "name": "window",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1h",
                "24h",
                "7d"
              ],
              "default": "24h"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The ranking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Trending"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          },
          "503": {
            "description": "`trending_unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}/orders": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        }
      ],
      "get": {
        "tags": [
          "Trading on behalf"
        ],
        "summary": "The user's pending orders",
        "description": "Scope `accounts:read`. `?account_id=` narrows to one of the user's accounts.",
        "operationId": "partnerListOrders",
        "parameters": [
          {
            "name": "account_id",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The pending orders (status `pending`).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "orders": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Position"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Trading on behalf"
        ],
        "summary": "Place an order for the user",
        "description": "Scope `trading:act`. Places a market / limit / stop order on one of the user's trading\naccounts through the SAME order path the trading platform uses (the account's leverage, spread, lot\nrules, margin and every CRM guard apply; the position is the user's). The brand's partner API switch and\nthe user's trading state are checked (`403 brand_disabled`, `company_inactive`, `brand_inactive`,\n`client_inactive`, `trading_inactive`). Every action is written to the client's history as done by\nthis API key. Idempotent per key by `client_order_id` (body or `Idempotency-Key` header): a repeat\nreturns `200 replayed` with the first answer.\n",
        "operationId": "partnerPlaceOrder",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string",
              "maxLength": 64
            },
            "description": "Used as client_order_id when the body has none."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrderRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`replayed` - the same client_order_id was placed before.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResult"
                }
              }
            }
          },
          "201": {
            "description": "`placed` - the order / position.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResult"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "`invalid_request` (details.errors) or a refusal of the order path: `order_refused`, `insufficient_margin`, `invalid_size`, `invalid_levels`, `invalid_price`, `invalid_leverage`, `market_closed`, `symbol_not_allowed`, `too_many_orders` (details.reason / details.errors). Also `409 mt_account` (an MT4 / MT5 account is not traded here), `503 no_price` / `balance_unavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}/orders/lookup": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        }
      ],
      "get": {
        "tags": [
          "Trading on behalf"
        ],
        "summary": "Find the order / position this key placed with a client_order_id",
        "description": "Scope `accounts:read`. `?client_order_id=` - `404 not_found` when this key never placed it.",
        "operationId": "partnerLookupOrder",
        "parameters": [
          {
            "name": "client_order_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The order (pending) or position (open / closed) with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "client_order_id": {
                      "type": "string"
                    },
                    "order": {
                      "$ref": "#/components/schemas/Position"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}/orders/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          },
          "description": "The pending order's id."
        }
      ],
      "patch": {
        "tags": [
          "Trading on behalf"
        ],
        "summary": "Change a pending order (price, stop loss, take profit)",
        "description": "Scope `trading:act`. Body `{ \"price\": 1.0850, \"sl\": 1.08, \"tp\": null, \"client_order_id\": \"...\" }` -\na field left out keeps its value, `null` clears it. `409 not_pending` when the order was filled already.\nAnswers `200 modified` / `200 unchanged`.\n",
        "operationId": "partnerModifyOrder",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LevelsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The order after the change.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResult"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        }
      },
      "delete": {
        "tags": [
          "Trading on behalf"
        ],
        "summary": "Cancel a pending order",
        "description": "Scope `trading:act`. Answers `200 cancelled` / `200 already_cancelled`; `409 not_pending` once filled.",
        "operationId": "partnerCancelOrder",
        "responses": {
          "200": {
            "description": "Cancelled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResult"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}/positions/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          },
          "description": "The open position's id."
        }
      ],
      "patch": {
        "tags": [
          "Trading on behalf"
        ],
        "summary": "Change the stop loss / take profit of an open position",
        "description": "Scope `trading:act`. Body `{ \"sl\": 1.08, \"tp\": 1.10, \"client_order_id\": \"...\" }` - a field left out keeps\nits value, `null` clears it. `409 pending_order` for an order not filled yet (use the orders routes).\n",
        "operationId": "partnerModifyPosition",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LevelsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`modified` / `unchanged` - the position after the change.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResult"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        }
      }
    },
    "/api/external/v1/users/{external_id}/positions/{id}/close": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ExternalId"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          },
          "description": "The open position's id."
        }
      ],
      "post": {
        "tags": [
          "Trading on behalf"
        ],
        "summary": "Close an open position at market",
        "description": "Scope `trading:act`. Body `{ \"client_order_id\": \"...\" }` (optional, or the `Idempotency-Key` header).\nAnswers `200 closed` / `200 already_closed`.\n",
        "operationId": "partnerClosePosition",
        "responses": {
          "200": {
            "description": "The closed position.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResult"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        }
      }
    },
    "/api/sso/brands/{brand_id}/login": {
      "parameters": [
        {
          "name": "brand_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          },
          "description": "The brand id shown in CRM Settings > API & Webhooks > SSO."
        }
      ],
      "get": {
        "tags": [
          "Single sign-on"
        ],
        "summary": "Sign a client in with a token your system signed (browser link)",
        "description": "No API key: the signed token is the credential. Your system signs a short JWT for the client and sends the\nbrowser here; we verify it and redirect to the brand's trader or client dashboard with a one-time login\ntoken. On a refusal the browser is sent to the app's `/login?sso_error=<code>` (or JSON with `?format=json`).\n\n**Token** (JWT, `RS256` with the public key / JWKS URL you gave us, or `HS256` with the shared secret):\n\n| claim | meaning |\n|---|---|\n| `iss` | exactly the issuer saved for the brand |\n| `aud` | exactly the audience saved for the brand |\n| `sub` | **your** client id (`external_id`, 1-191 letters, digits, `. _ @ : + -`) |\n| `iat` | issued at (unix seconds); older than `max_age_seconds` (default 300) is refused |\n| `exp` | expiry (unix seconds); at most `max_age_seconds` after `iat` |\n| `jti` | unique token id - a token presented twice is refused (`token_replayed`) |\n| `nbf`, `email`, `name`, `given_name`, `family_name` | optional; the names / e-mail fill a client created by auto-create |\n| header `kid` | the key id when a JWKS URL is used |\n\nClock skew of `clock_skew_seconds` (default 60) is tolerated. Unknown client id: a new client is created\nwhen the brand's \"create unknown clients\" is on, else `404 user_not_found`.\n",
        "operationId": "ssoLogin",
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "target",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "trader",
                "dashboard"
              ],
              "default": "trader"
            }
          },
          {
            "name": "format",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "json"
              ]
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to `https://<brand app>/login?token=<one-time token>` (or `/login?sso_error=<code>`)."
          },
          "401": {
            "description": "`token_invalid` (not a JWT / bad header or claims), `token_signature`, `token_alg` (the algorithm is not the one configured for the brand), `token_issuer`, `token_audience`, `token_expired`, `token_age` (issued more than the allowed age ago, or not yet valid: iat / nbf outside the window), `token_jti` (missing jti), `token_subject` (missing sub), `token_key` (no key found for the token's kid), `token_replayed` (the jti was already used).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "`sso_disabled`, `sso_not_configured`, `login_disabled`, `user_other_brand`, `company_inactive`, `brand_inactive`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "`brand_not_found`, `user_not_found`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`no_app_domain`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`invalid_target`, `invalid_subject`, `create_failed`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Single sign-on"
        ],
        "summary": "Sign a client in with a token your system signed (server to server)",
        "description": "The same check as GET; answers the login link as JSON for your backend or page script.",
        "operationId": "ssoLoginJson",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string"
                  },
                  "target": {
                    "type": "string",
                    "enum": [
                      "trader",
                      "dashboard"
                    ],
                    "default": "trader"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The link.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/LoginLink"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "created": {
                          "type": "boolean",
                          "description": "The client was created by this call (auto-create)."
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Invalid"
          }
        }
      }
    }
  },
  "webhooks": {
    "client.created": {
      "post": {
        "summary": "A client was created",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    },
    "client.updated": {
      "post": {
        "summary": "A client's tracked fields changed (`data.changed_fields`)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    },
    "client.agent_changed": {
      "post": {
        "summary": "The assigned agents changed (`data.previous_agents`)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    },
    "client.deleted": {
      "post": {
        "summary": "A client was deleted (`data.client` = id + deleted_at only)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    },
    "client.login": {
      "post": {
        "summary": "A client signed in (password, link or single sign-on)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    },
    "client.inactive": {
      "post": {
        "summary": "A client has not signed in for N days (the brand's \"inactive after\" setting)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InactiveEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    },
    "account.created": {
      "post": {
        "summary": "A trading account was opened",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AccountEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    },
    "trade.opened": {
      "post": {
        "summary": "A position was opened",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TradeEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    },
    "trade.modified": {
      "post": {
        "summary": "A position's stop loss / take profit / amount changed",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TradeEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    },
    "trade.closed": {
      "post": {
        "summary": "A position was closed (`data.position.closed_by` = user, tp, sl, stop_out, admin, ...)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TradeEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    },
    "demo.first_trade": {
      "post": {
        "summary": "The client's first trade on a demo account",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TradeEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    },
    "margin.call": {
      "post": {
        "summary": "The client's margin level fell to the brand's margin call level",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MarginEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    },
    "stop.out": {
      "post": {
        "summary": "Positions were closed by the stop-out rule",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MarginEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    },
    "price_alert.triggered": {
      "post": {
        "summary": "A client's price alert fired (the level was crossed)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PriceAlertEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    },
    "deposit.status": {
      "post": {
        "summary": "A deposit changed status (pending, approved, declined, cancelled)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MoneyEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    },
    "withdrawal.status": {
      "post": {
        "summary": "A withdrawal changed status (pending, approved, declined, cancelled)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MoneyEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    },
    "webhook.test": {
      "post": {
        "summary": "The \"Send test event\" button of the Octolit CRM",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Event"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Api-Key"
      },
      "ApiKeyBearer": {
        "type": "http",
        "scheme": "bearer"
      }
    },
    "parameters": {
      "ExternalId": {
        "name": "external_id",
        "in": "path",
        "required": true,
        "description": "Your client id (1-191 letters, digits, `. _ @ : + -`). Stored on the client as `external_user_id`.",
        "schema": {
          "type": "string",
          "maxLength": 191
        }
      }
    },
    "responses": {
      "User": {
        "description": "The user.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "user": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "No key, an unknown / expired / revoked key, or a refused token.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "`insufficient_scope`, `ip_not_allowed`, `api_disabled`, `company_inactive`, `brand_disabled`, `sso_disabled`, `trending_disabled` (trading on behalf also: `brand_inactive`, `client_inactive`, `trading_inactive`, `trading_denied`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "`not_found` / `user_not_found` / `account_not_found` / `symbol_not_found`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "`email_taken`, `brand_fixed`, `limit_reached`, `already_linked`, `no_brand`, `no_app_domain`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Invalid": {
        "description": "`invalid_request` with `details.errors` = `{ field: reason }`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "`rate_limited` (see `Retry-After`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object"
              }
            }
          }
        }
      },
      "ApiKeyInfo": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "prefix": {
            "type": "string"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "clients:read",
                "clients:read_email",
                "clients:read_phone",
                "clients:read_ftd",
                "clients:read_comments",
                "clients:read_custom_fields",
                "users:write",
                "sso:issue",
                "accounts:read",
                "accounts:write",
                "trading:control",
                "mt:link",
                "trading:act",
                "balance:write"
              ]
            }
          },
          "ip_allowlist": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "expired",
              "revoked"
            ]
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Client": {
        "type": "object",
        "description": "The CRM's client. `email` / `phone` are masked unless the key carries `clients:read_email` / `clients:read_phone`.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "full_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "emails": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "phones": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "brand": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "desk": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "sales_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "sales_status_changed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "kyc": {
            "type": "object",
            "properties": {
              "id_status": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "billing_status": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "active": {
            "type": "boolean"
          },
          "is_lead": {
            "type": "boolean"
          },
          "deleted": {
            "type": "boolean"
          },
          "country": {
            "type": [
              "string",
              "null"
            ]
          },
          "external_user_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your client id."
          },
          "agents": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "name": {
                  "type": "string"
                }
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "first_deposit_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Only with clients:read_ftd."
          },
          "comments": {
            "type": "array",
            "description": "Only with clients:read_comments."
          },
          "custom_fields": {
            "type": "object",
            "description": "Only with clients:read_custom_fields."
          }
        }
      },
      "User": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Client"
          },
          {
            "type": "object",
            "properties": {
              "external_id": {
                "type": "string"
              },
              "login_enabled": {
                "type": "boolean",
                "description": "The client may sign in."
              },
              "trading_enabled": {
                "type": "boolean",
                "description": "false after trading/disable."
              },
              "accounts": {
                "type": "integer",
                "description": "Number of visible trading accounts."
              }
            }
          }
        ]
      },
      "UserInput": {
        "type": "object",
        "properties": {
          "first_name": {
            "type": "string",
            "maxLength": 120
          },
          "last_name": {
            "type": "string",
            "maxLength": 120
          },
          "full_name": {
            "type": "string",
            "maxLength": 120
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254
          },
          "phone": {
            "type": "string",
            "description": "With the country code",
            "e.g. \"+357 99 123456\".": null
          },
          "country": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2,
            "description": "ISO 3166-1 alpha-2."
          },
          "brand_id": {
            "type": "integer",
            "description": "Only read when the user is created."
          }
        }
      },
      "LoginLink": {
        "type": "object",
        "properties": {
          "login_url": {
            "type": "string",
            "format": "uri",
            "description": "https://<brand app>/login?token=<one-time token>"
          },
          "token": {
            "type": "string",
            "description": "The one-time token itself (for a page that signs in through the app's own /login?token=)."
          },
          "target": {
            "type": "string",
            "enum": [
              "trader",
              "dashboard"
            ]
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "client_id": {
            "type": "integer",
            "description": "The CRM's client id."
          }
        }
      },
      "Session": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "agent_session": {
            "type": "boolean",
            "description": "A CRM agent signed in as the client."
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "TradingAccount": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "demo": {
            "type": "boolean"
          },
          "type": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              },
              "provider": {
                "type": "string",
                "description": "internal or mt."
              }
            }
          },
          "balance": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "equity": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "margin": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "free_margin": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "margin_level": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "open_pl": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "closed_pl": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "credit": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "mt_login": {
            "type": [
              "string",
              "null"
            ],
            "description": "MT accounts only."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Balance": {
        "type": "object",
        "properties": {
          "currency": {
            "type": "string"
          },
          "balance": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "equity": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "margin": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "free_margin": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "margin_level": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "open_pl": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "credit": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "as_of": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "accounts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TradingAccount"
            }
          }
        }
      },
      "MoneyPosting": {
        "type": "object",
        "required": [
          "kind",
          "amount"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "deposit",
              "withdrawal",
              "credit",
              "credit_out"
            ],
            "description": "credit = credit in (a credit line)",
            "credit_out = credit removed.": null
          },
          "amount": {
            "type": [
              "string",
              "number"
            ],
            "description": "> 0, at most 10 decimals (send a string to keep the exact decimals)."
          },
          "currency": {
            "type": [
              "string",
              "integer"
            ],
            "description": "3-letter code or id; default the account's currency."
          },
          "account_id": {
            "type": "integer",
            "description": "One of the user's accounts; default the first one."
          },
          "status": {
            "type": "string",
            "enum": [
              "approved",
              "pending"
            ],
            "default": "approved"
          },
          "method": {
            "type": "string",
            "maxLength": 64,
            "description": "Payment method text shown in the Octolit CRM (default partner_api)."
          },
          "reference": {
            "type": "string",
            "maxLength": 128,
            "description": "Your reference (invoice / PSP id); shown in the Octolit CRM and sent back."
          },
          "note": {
            "type": "string",
            "maxLength": 255
          },
          "idempotency_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128,
            "description": "Required unless sent as the Idempotency-Key header."
          }
        }
      },
      "MoneyPostingResult": {
        "type": "object",
        "properties": {
          "transaction": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer",
                "description": "The CRM transaction id (the same id the deposit.status / withdrawal.status events carry)."
              },
              "kind": {
                "type": "string",
                "enum": [
                  "deposit",
                  "withdrawal",
                  "credit",
                  "credit_out"
                ]
              },
              "status": {
                "type": "string",
                "enum": [
                  "approved",
                  "pending"
                ]
              },
              "amount": {
                "type": [
                  "string",
                  "number"
                ]
              },
              "currency": {
                "type": "string"
              },
              "account_id": {
                "type": "integer"
              },
              "method": {
                "type": "string"
              },
              "reference": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "note": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "balance": {
            "$ref": "#/components/schemas/Balance",
            "description": "The user's balance after the posting."
          },
          "idempotency_key": {
            "type": "string"
          },
          "replayed": {
            "type": "boolean",
            "description": "true = this is the stored answer of an earlier call with the same key."
          }
        }
      },
      "Position": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "account_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "symbol": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "pending",
              "closed"
            ]
          },
          "side": {
            "type": "string",
            "enum": [
              "buy",
              "sell"
            ]
          },
          "order_type": {
            "type": [
              "integer",
              "null"
            ]
          },
          "amount": {
            "type": [
              "string",
              "number",
              "null"
            ],
            "description": "Margin used (account currency)."
          },
          "trade_amount": {
            "type": [
              "string",
              "number",
              "null"
            ],
            "description": "Position value (account currency)."
          },
          "units": {
            "type": [
              "number",
              "null"
            ]
          },
          "lot_size": {
            "type": [
              "number",
              "null"
            ]
          },
          "leverage": {
            "type": [
              "number",
              "null"
            ]
          },
          "open_price": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "stop_loss": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "take_profit": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "profit": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "commission": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "swap": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "market": {
            "type": [
              "string",
              "null"
            ]
          },
          "opened_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "closed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "closed_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "Quote": {
        "type": "object",
        "properties": {
          "symbol_id": {
            "type": "integer"
          },
          "symbol": {
            "type": "string"
          },
          "pair": {
            "type": "string",
            "description": "BASE-QUOTE as the price feed names it."
          },
          "bid": {
            "type": [
              "number",
              "null"
            ]
          },
          "ask": {
            "type": [
              "number",
              "null"
            ]
          },
          "mid": {
            "type": [
              "number",
              "null"
            ]
          },
          "ts": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Epoch ms of the quote."
          }
        }
      },
      "Bar": {
        "type": "object",
        "required": [
          "t",
          "o",
          "h",
          "l",
          "c"
        ],
        "properties": {
          "t": {
            "type": "integer",
            "description": "Bar open time",
            "epoch seconds (UTC).": null
          },
          "o": {
            "type": "number"
          },
          "h": {
            "type": "number"
          },
          "l": {
            "type": "number"
          },
          "c": {
            "type": "number"
          },
          "v": {
            "type": [
              "number",
              "null"
            ],
            "description": "Tick volume when the feed has it."
          }
        }
      },
      "Bars": {
        "type": "object",
        "properties": {
          "symbol_id": {
            "type": "integer"
          },
          "symbol": {
            "type": "string"
          },
          "pair": {
            "type": "string"
          },
          "resolution": {
            "type": "string",
            "enum": [
              "1m",
              "5m",
              "15m",
              "30m",
              "1h",
              "4h",
              "1D",
              "1W",
              "1M"
            ]
          },
          "from": {
            "type": [
              "integer",
              "null"
            ]
          },
          "to": {
            "type": "integer"
          },
          "count": {
            "type": "integer"
          },
          "bars": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Bar"
            }
          },
          "complete": {
            "type": "boolean",
            "description": "true = nothing older is stored (or the window start was reached)."
          },
          "next_to": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Pass as `to` to read the older bars (null when complete)."
          }
        }
      },
      "TrendingSymbol": {
        "type": "object",
        "properties": {
          "rank": {
            "type": "integer"
          },
          "symbol": {
            "type": "string",
            "description": "BASE-QUOTE pair."
          },
          "ticker": {
            "type": [
              "string",
              "null"
            ],
            "description": "The symbol name as the platform shows it."
          },
          "ticker_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "trades": {
            "type": "integer",
            "description": "Orders opened in the window by the brand's clients."
          },
          "clients": {
            "type": "integer",
            "description": "Distinct clients who traded it."
          },
          "share_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "Share of all the brand's orders in the window (percent)."
          },
          "buy_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "Percentage of those orders that were buys; null below 10 clients."
          }
        }
      },
      "Trending": {
        "type": "object",
        "properties": {
          "now": {
            "type": "integer",
            "description": "Epoch ms."
          },
          "as_of": {
            "type": "integer",
            "description": "Epoch ms the ranking was computed (cached up to a minute)."
          },
          "scope": {
            "type": "string",
            "enum": [
              "brand"
            ]
          },
          "window": {
            "type": "string",
            "enum": [
              "1h",
              "24h",
              "7d"
            ]
          },
          "from": {
            "type": "integer",
            "description": "Epoch ms",
            "window start.": null
          },
          "min_clients": {
            "type": "integer"
          },
          "total_trades": {
            "type": "integer"
          },
          "symbols": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TrendingSymbol"
            }
          }
        }
      },
      "OrderRequest": {
        "type": "object",
        "required": [
          "trading_account_id",
          "side",
          "type"
        ],
        "properties": {
          "client_order_id": {
            "type": "string",
            "maxLength": 64,
            "description": "Your id of this order (idempotency per key, letters digits _ - : .); or send the `Idempotency-Key` header."
          },
          "trading_account_id": {
            "type": "integer",
            "description": "One of the user's trading accounts."
          },
          "symbol_id": {
            "type": "integer",
            "description": "The symbol id (or `symbol`)."
          },
          "symbol": {
            "type": "string",
            "description": "The symbol name",
            "e.g. EURUSD.": null
          },
          "side": {
            "type": "string",
            "enum": [
              "buy",
              "sell"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "market",
              "limit",
              "stop"
            ]
          },
          "units": {
            "type": "number",
            "description": "Size in units (or `lots`)."
          },
          "lots": {
            "type": "number",
            "description": "Size in lots (converted with the symbol's contract size)."
          },
          "leverage": {
            "type": "integer",
            "description": "One of the leverages the account offers; default the symbol's."
          },
          "sl": {
            "type": [
              "number",
              "null"
            ]
          },
          "tp": {
            "type": [
              "number",
              "null"
            ]
          },
          "price": {
            "type": "number",
            "description": "Required for limit / stop."
          }
        }
      },
      "LevelsRequest": {
        "type": "object",
        "properties": {
          "price": {
            "type": [
              "number",
              "null"
            ],
            "description": "Pending orders only."
          },
          "sl": {
            "type": [
              "number",
              "null"
            ]
          },
          "tp": {
            "type": [
              "number",
              "null"
            ]
          },
          "client_order_id": {
            "type": "string",
            "maxLength": 64
          }
        }
      },
      "OrderResult": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "placed",
              "replayed",
              "modified",
              "unchanged",
              "cancelled",
              "already_cancelled",
              "closed",
              "already_closed"
            ]
          },
          "client_order_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "order": {
            "$ref": "#/components/schemas/Position",
            "description": "The order (orders routes)."
          },
          "position": {
            "$ref": "#/components/schemas/Position",
            "description": "The position (positions routes)."
          }
        }
      },
      "MtLink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "platform": {
            "type": "string",
            "enum": [
              "mt4",
              "mt5"
            ]
          },
          "server": {
            "type": "string"
          },
          "login": {
            "type": "string"
          },
          "label": {
            "type": [
              "string",
              "null"
            ]
          },
          "trading_account_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Event": {
        "type": "object",
        "required": [
          "id",
          "type",
          "created_at",
          "company_id",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "evt_... - the same on every retry and resend."
          },
          "type": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "company_id": {
            "type": "integer"
          },
          "data": {
            "type": "object"
          }
        }
      },
      "ClientEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "properties": {
                  "client": {
                    "$ref": "#/components/schemas/Client"
                  },
                  "changed_fields": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string"
                    },
                    "description": "client.updated only."
                  },
                  "previous_agents": {
                    "type": "array",
                    "description": "client.agent_changed only."
                  }
                }
              }
            }
          }
        ]
      },
      "LoginEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "properties": {
                  "client": {
                    "$ref": "#/components/schemas/ClientRef"
                  },
                  "login": {
                    "type": "object",
                    "properties": {
                      "method": {
                        "type": "string",
                        "enum": [
                          "password",
                          "link",
                          "sso",
                          "agent"
                        ],
                        "description": "password, a mailed sign-in link, your SSO token / the partner API, or a CRM agent signing in as the client."
                      },
                      "two_factor": {
                        "type": "boolean",
                        "description": "The second sign-in step (code) was passed."
                      },
                      "app": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "trader",
                          "dashboard",
                          null
                        ]
                      },
                      "ip": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "device": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "Browser / system from the user agent, e.g. \"Chrome / Windows\"."
                      },
                      "at": {
                        "type": "string",
                        "format": "date-time"
                      }
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "InactiveEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "properties": {
                  "client": {
                    "$ref": "#/components/schemas/ClientRef"
                  },
                  "last_login_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "inactive_days": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        ]
      },
      "AccountEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "properties": {
                  "client": {
                    "$ref": "#/components/schemas/ClientRef"
                  },
                  "account": {
                    "$ref": "#/components/schemas/TradingAccount"
                  },
                  "created_by": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "client",
                    "agent": null,
                    "api or null when not recorded.": null
                  }
                }
              }
            }
          }
        ]
      },
      "TradeEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "properties": {
                  "client": {
                    "$ref": "#/components/schemas/ClientRef"
                  },
                  "position": {
                    "$ref": "#/components/schemas/Position"
                  },
                  "changed_fields": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "trade.modified only."
                  }
                }
              }
            }
          }
        ]
      },
      "MarginEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "properties": {
                  "client": {
                    "$ref": "#/components/schemas/ClientRef"
                  },
                  "account_id": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "margin_level": {
                    "type": [
                      "string",
                      "number",
                      "null"
                    ],
                    "description": "The account's margin level in percent when the event was raised."
                  },
                  "level": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "The brand's margin call level in percent (CRM Settings > API & Webhooks > Broker login, default 100). margin.call is sent once when the account goes under it and again every hour while it stays under; stop.out when positions were closed by the margin rule."
                  },
                  "equity": {
                    "type": [
                      "string",
                      "number",
                      "null"
                    ]
                  },
                  "margin": {
                    "type": [
                      "string",
                      "number",
                      "null"
                    ]
                  },
                  "closed_positions": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "description": "stop.out only."
                  }
                }
              }
            }
          }
        ]
      },
      "PriceAlertEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "properties": {
                  "client": {
                    "$ref": "#/components/schemas/ClientRef"
                  },
                  "alert": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "integer"
                      },
                      "account_id": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "The trading account the alert's price type belongs to."
                      },
                      "symbol": {
                        "type": "string",
                        "description": "The pair as the trader shows it (EUR/USD)."
                      },
                      "price_kind": {
                        "type": "string",
                        "enum": [
                          "bid",
                          "ask"
                        ]
                      },
                      "direction": {
                        "type": "string",
                        "enum": [
                          "above",
                          "below"
                        ],
                        "description": "The side of the level the alert waited for."
                      },
                      "price": {
                        "type": [
                          "string",
                          "number"
                        ],
                        "description": "The alert level."
                      },
                      "triggered_price": {
                        "type": [
                          "string",
                          "number",
                          "null"
                        ],
                        "description": "The price that crossed it."
                      },
                      "triggered_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "note": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "The client's own note on the alert."
                      }
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "MoneyEvent": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Event"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "properties": {
                  "client": {
                    "$ref": "#/components/schemas/ClientRef"
                  },
                  "transaction": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "integer"
                      },
                      "kind": {
                        "type": "string",
                        "enum": [
                          "deposit",
                          "withdrawal"
                        ]
                      },
                      "status": {
                        "type": "string",
                        "enum": [
                          "pending",
                          "approved",
                          "declined",
                          "cancelled",
                          "refunded"
                        ]
                      },
                      "previous_status": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "amount": {
                        "type": [
                          "string",
                          "number"
                        ]
                      },
                      "currency": {
                        "type": "string"
                      },
                      "account_id": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "method": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "at": {
                        "type": "string",
                        "format": "date-time"
                      }
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "ClientRef": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your client id (external_user_id)."
          },
          "brand": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}
