# AUDIT Source: https://docs.anthid.com/AUDIT # Documentation accuracy review ## 2026-10-08 source review Reviewed all 23 app directories against the local Rust handlers, router registration, request and response types, authorization, services, and error conversion. The source checkout is `/home/dallin/projects/anthid`, verified against `git@github.com:anthid-labs/anthid.git`. Work is isolated on `codex/anthid-openapi-audit`; the source checkout's unrelated lockfile edit is preserved. ### App coverage Counts below exclude health, metrics, and OpenAPI utility routes. | App | Contract reviewed | Publication | | - | - | - | | accounts | 18 REST operations | Public OpenAPI | | billing | 4 REST operations | Public OpenAPI, 2 internal operations hidden | | connection | 2 REST operations | Public OpenAPI | | controls | 32 REST operations | Public OpenAPI | | instruments | 3 REST operations | Public OpenAPI | | intents | 6 REST operations | Public OpenAPI | | ledger | 14 REST operations | Public OpenAPI | | organizations | 13 REST operations | Public OpenAPI, WorkOS webhook hidden | | auth | 5 exported REST operations and 5 internal handlers | Separate Utoipa document; outside the public site | | notifications | 2 REST operations | Separate Utoipa document; outside the public site | | contact\_form | 1 submission handler | No Utoipa document | | market | 5 HTTP operations, restricted by organization | No Utoipa document; excluded from public reference | | tradestation | 1 token exchange handler, static API key | No Utoipa document | | streamer | 2 gRPC methods, health and reflection | Protobuf contract | | clients\_manager | 2 worker binaries | No HTTP API | | scheduler | NATS/KV worker | No HTTP API | | webapp | Vite client | No server OpenAPI contract | | tradestation-web | Vite OAuth client | No server OpenAPI contract | | marketing | Prerendered site and GET/HEAD content middleware | No Utoipa document | | sanity | CMS studio | No Utoipa document | | docs | Mintlify site and API export tooling | Eight public specifications | ### Corrections * Shortened endpoint and schema prose while retaining access rules, defaults, limits, and error conditions. * Added missing service-token security declarations and corrected organization membership and onboarding access rules. * Corrected account-control entitlement requirements, PUT/PATCH semantics, symbol-list scope, and evaluation's required nullable body. * Corrected empty authentication/rate-limit responses, JSON versus plain-text errors, parser rejection statuses, and the intent control-refusal union. * Corrected ledger history windows, latency sample thresholds, fee examples, and New York daily bucket timestamps. * Added source exporters for the seven public services that lacked them. All eight use the existing publication filter workflow. * Added the missing intent void and short-rate navigation entries and guides. Removed obsolete instrument filters from the catalog guide. * Updated the downloadable intent guidance for environment filters, authorization, voiding, and parser errors. * Corrected internal Market session schema descriptions without publishing its API. ### Runtime fixes The three findings from the source review are now fixed: * Shared auth error conversion preserves caller `BadRequest` as 400 and `Unauthorized` as 401, while policy denials remain 403. Submission and void requests preserve an unknown account as 404. Token exchange, transport, upstream server, and parsing failures remain 500. Regression tests check actual response status and JSON bodies. * Control-history cursors carry both timestamp and event ID. Database regressions cover limit 1, tied timestamps, complete traversal, tenant/account filtering, and omitted private response fields. Earlier timestamp-only cursors return 400 and ask the caller to restart pagination, because they cannot resume without repeating or skipping events. SQLx metadata was regenerated from the real Controls schema in a disposable local database. * Auth terms lookup selects the latest `accepted_at`, then the greatest record UUID for deterministic timestamp ties. Version labels are not treated as dates. Database regressions cover ties, user isolation, retry behavior, and absent acceptance. No persistence schema, stream contract, or production configuration changed. The Controls package reuses the workspace's existing base64 dependency for opaque cursors. No dependency version was changed. Notifications publication remains unverified and outside the explicit public-service registry. Browser visual QA remains unavailable because no browser is connected. ### Verification Run from the repository root unless stated otherwise: * `cargo fmt --check`: passed. * `SQLX_OFFLINE=true cargo clippy --offline --locked -p api -p utils -p accounts -p auth -p billing -p connection -p contact_form -p controls -p instruments -p intents -p ledger -p market -p notifications -p organizations --all-targets --all-features -- -D warnings`: passed. * `SQLX_OFFLINE=true cargo test --offline --locked -p api -p utils -p accounts -p auth -p billing -p connection -p contact_form -p controls -p instruments -p intents -p ledger -p market -p notifications -p organizations --lib --bins --tests`: passed, 1,275 tests across 54 test binaries. This default-feature run excludes integration-gated tests. * `SQLX_OFFLINE=true DATABASE_URL= CONTROL_HISTORY_TEST_DATABASE_URL= cargo test --offline --locked -p controls -p auth -p utils -p intents --features controls/integration,auth/integration --lib`: passed, 253 tests, including three Auth database cases and one Controls database case. Other database/broker integration suites were not run. * `DATABASE_URL= SQLX_OFFLINE=false cargo sqlx prepare -- --lib --offline`, from `apps/controls`: passed; the changed query metadata is checked in. The container used real Controls migrations and was removed after validation. * `SQLX_OFFLINE=true cargo run --offline --locked -p --example export_openapi`: passed for all eight public services. Raw exports were passed to `generate-openapi.mjs --local --input =`; the same invocation with `--check` passed without writes. * `node apps/docs/api-reference/scripts/generate-openapi.mjs --local --check`: passed. * `node apps/docs/api-reference/scripts/validate-reference.mjs`: passed for eight specs, 92 operations, schema/security references, and navigation. * `node --test apps/docs/api-reference/scripts/reference.test.mjs`: nine tests passed. The sandbox denied child-process creation; the approved unsandboxed rerun passed. * `mint validate --telemetry false` and `mint broken-links --telemetry false`, from `apps/docs`: passed. Mintlify required approved execution outside the sandbox for its network-interface check. * `mint dev --port 3037 --telemetry false`: local preview started. HTTP smoke checks returned 200 for Instruments and Intents overviews and found the new short-rate and void-command text. Browser visual QA was unavailable because no browser was connected. * `git diff --check`: passed. The user subsequently authorized the three runtime fixes and a draft PR on this branch. No merge, deployment, shared-database migration, or production API call is part of this work. The prior prop validation remains paused. ## 2026-09-18 review Compared the public API handlers, shared request and response models, changes since the September 9 review, deployed OpenAPI exports, and streaming models at the `rust_types` revision pinned by `Cargo.lock` (`9829d4c`). Local protobuf definitions were also checked. This is a documentation and contract review, not a live authenticated trading test. Market and strategy APIs are intentionally excluded at the owner's direction. They are private, under development, and access or feature gated. They have no public navigation or exports. The exporter also omits the private strategy entitlement from billing schemas. ### Corrections | Area | Correction | Evidence | | - | - | - | | Missing public reference | Added the Instruments export, three endpoints, catalog guide, authentication, alias lookup, filtering, and cursor rules | Public `/v1/instruments/openapi.json`; `apps/instruments/src/api/handlers/instruments.rs`, `src/policy/instrument.rs`; `libs/crates/api/src/instruments_api` | | Stream retention | Documented one-hour expiry for terminal orders and zero positions, no public expiry event, and fresh cache reconstruction | Pinned `rust_types/src/streams/state/retention.rs`; `apps/streamer/src/services/subscription_manager/consumer.rs` | | Execution metadata | Added optional execution identity, order totals, broker timestamp, resolved instrument, and applied command sequence | Pinned `rust_types/src/broker/trade.rs`; `proto/trading/trading_models.proto` | | Reconnect guidance | Preserve live updates while reconciling REST; filter current orders for open state; update all client guides and downloadable guidance | Stream consumer; ledger latest-state handlers and models | | Daily returns | Explain broker position reconciliation, recovered trades, unpriced inventory corrections, partial closes, dollar amounts, and fee exclusions | `apps/ledger/src/services/orders/get_daily_returns.rs`, `reconcile.rs`, `realized.rs`; ledger query and response models | | Wire conventions | Separate decimal values from integer counts, distinguish tagged union formats, document REST versus stream class casing, and correct bearer-token streaming support | Shared API models; protobuf subscription model; streamer authentication | | Drift checks | Share an explicit public service registry, require visible operations in navigation, resolve auth schemes, reject private/unregistered exports, add read-only export comparison, and stage all fetches before writes | `api-reference/scripts/services.mjs`, `generate-openapi.mjs`, `validate-reference.mjs`, `reference.test.mjs` | The existing seven deployed exports match their checked-in contracts after publication filtering. The instrument export adds three operations, bringing validation to eight specs and 91 method/path pairs, including hidden operations. No new broker support is inferred from catalog schemas. ### Publication gaps and follow-up * Notifications has two authenticated read handlers in source, but `https://api.anthid.com/v1/notifications/openapi.json` returned HTTP 404 during the audit. It remains outside public documentation pending confirmation of deployment and intended publication. * The exporter still reports the existing retired-broker name in `BrokerCredentialVariant.description`. It explicitly describes an unsupported variant; retired credential variants remain structurally removed. ### Verification Run from `anthid/` unless noted: * `node apps/docs/api-reference/scripts/generate-openapi.mjs --check`: passed against all eight deployed public exports after publication filtering, without file changes. * `node apps/docs/api-reference/scripts/generate-openapi.mjs --local --check`: passed; local publication processing is stable. * `node apps/docs/api-reference/scripts/validate-reference.mjs`: passed for eight specs, 91 source endpoints, schema and security references, and navigation coverage. * `node --test apps/docs/api-reference/scripts/reference.test.mjs`: seven tests passed, including missing navigation, missing source coverage, hidden operations, broken references, private exports, no-write checks, and failed-refresh preservation. The sandbox initially denied child Node processes; rerunning with the approved execution permission passed. * `mint validate --telemetry false`, from `apps/docs/`: passed. The sandbox initially blocked Mintlify's network-interface check; rerunning with the approved execution permission passed. * `mint broken-links --telemetry false`, from `apps/docs/`: passed with no broken links. * `mint dev --port 3034 --telemetry false`, from `apps/docs/`: previewed the Instruments overview and generated list endpoint in the browser; confirmed navigation, rendered content, and examples. Stopped the preview afterward. * `git diff --check`: passed. Only `apps/docs` and its export/validation tooling changed. No Rust package or service binary, database schema, or stream contract changed. No authenticated broker calls, deployment, migrations, secrets, or production configuration changes were made. Publish the documentation through its normal release process; runtime contract deployment was not verified by this review. ## Previous review: 2026-09-09 Reviewed on 2026-09-09 against the monorepo implementations, the local shared protobuf definitions, and public REST OpenAPI exports. ## Changes and evidence | Area | Correction | Implementation checked | | - | - | - | | OMS workflows | Added the dashboard walkthrough, including order ticket, intent and order detail, streams, and analytics | `apps/webapp/src/lib/routes.ts`, `apps/webapp/src/lib/navigation.ts` | | Positioning | Described order and execution management through the API and dashboard; separated current analytics from roadmap work | `apps/marketing/src/content/hero.ts`, `apps/marketing/src/content/capabilities.ts`, dashboard navigation | | Controls | Documented create-only evaluation, no automatic cancellation of resting orders, and stored but unenforced stale-order and loss/position limits | `apps/intents/src/services/control/submit_intent.rs`, `apps/controls/src/services/evaluate/engine.rs`, `risk.rs`, `time_window.rs` | | HTTP retries | Removed idempotent-submission promises from blog copy; documented that correlation keys do not deduplicate repeated creates | `libs/crates/extractors/src/ingress.rs`, `apps/intents/src/services/control/create_order.rs`, `apps/intents/src/database/create_intent.rs` | | Intent identity | Corrected downloadable guidance: replace and cancel append actions to the original intent | `apps/intents/src/services/control/replace.rs`, `cancel.rs` | | Archival | Explained asynchronous archival and cleanup; corrected the claim that the operational action trigger prohibits deletion | `apps/intents/src/database/terminal/sweep.rs`, `apps/intents/migrations/01_intent_action.sql` | | Streaming | Added primed order delivery, position priming, completion markers, and instrument filtering; corrected reconnect guidance | `apps/streamer/src/services/subscription_manager/consumer.rs`, `scope.rs`, shared `proto/trading/trading_service.proto` | | Client examples | Corrected Node request casing, Rust subscription fields and exhaustive event matching; added priming guidance for all three clients | Current shared protobuf messages; Node example's `keepCase: false` setting | | Limits | Added billing claims reference and a guide distinguishing retained records from plan query windows | `apps/billing/src/api/handlers/claims.rs`, `apps/billing/src/services/claims/mod.rs`, ledger migrations and services | | API reference | Refreshed public exports, added billing export, hid operator billing overrides, and added a repeatable source/navigation/reference check | Seven services' `src/api/handlers` annotations and exported schemas | | Theme | Default dark mode with the light/dark toggle available | `appearance.default: dark`, `appearance.strict: false`, [Mintlify appearance settings](https://www.mintlify.com/docs/organize/settings-appearance) | The broker pages' order-type coverage remains consistent with the Lightspeed adapter and intent request model. No new broker support is inferred from shared enum or schema names. ## Verification Run from `anthid/` unless a different directory is noted: * `node apps/docs/api-reference/scripts/generate-openapi.mjs`: fetched all seven public REST specifications. * `node apps/docs/api-reference/scripts/generate-openapi.mjs --local`: processing completed, including hiding the operator-only billing override route. * `node apps/docs/api-reference/scripts/validate-reference.mjs`: passed for seven specs and 88 method/path pairs, local schema references, and navigation. * `mint validate --telemetry false`, from `apps/docs/`: passed. * `mint broken-links --telemetry false`, from `apps/docs/`: passed with no broken links. * `cargo check --offline --manifest-path /tmp/anthid-docs-rust-check/Cargo.toml`: passed for the Rust server-streaming example and its event match, extracted into a temporary crate and generated against the local protobuf definitions. This did not connect to a broker or streaming service. * `git diff --check`: passed. Method/path parity does not prove deployed business behavior or schema equivalence with every Rust type. Streaming behavior was checked against source, not a live authenticated session. No broker orders, migrations, deployment, or credential/configuration changes were made. Only the docs app and its export/validation tooling are affected; no service binaries, database schemas, or stream contracts changed. # Delete account control Source: https://docs.anthid.com/api-reference/account-manual-controls/delete-account-control /api-reference/controls.openapi.json delete /v1/controls/accounts/{account_id} Deletes manual controls for a trading account in the caller's organization. Requires Controls Basic or Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Get account control Source: https://docs.anthid.com/api-reference/account-manual-controls/get-account-control /api-reference/controls.openapi.json get /v1/controls/accounts/{account_id} Returns manual controls for a trading account. Users and API keys require Controls Basic or Premium and control read access. Services need `control:read` and `x-organization-id`. The organization must own the account. # List control changes for an account Source: https://docs.anthid.com/api-reference/account-manual-controls/list-control-changes-for-an-account /api-reference/controls.openapi.json get /v1/controls/accounts/{account_id}/history Lists account and symbol control changes, newest first. Includes the actor's credential type, without actor identifiers. Requires Controls Basic or Premium and control read access for the account. # Patch account control Source: https://docs.anthid.com/api-reference/account-manual-controls/patch-account-control /api-reference/controls.openapi.json patch /v1/controls/accounts/{account_id} Creates or patches manual controls for a trading account. Omitted and null fields are unchanged; `clear` removes named fields. Requires Controls Basic or Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Upsert account control Source: https://docs.anthid.com/api-reference/account-manual-controls/upsert-account-control /api-reference/controls.openapi.json put /v1/controls/accounts/{account_id} Creates or replaces manual controls for a trading account in the caller's organization. Omitted optional fields are cleared. Requires Controls Basic or Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Delete account risk control Source: https://docs.anthid.com/api-reference/account-risk-controls/delete-account-risk-control /api-reference/controls.openapi.json delete /v1/controls/accounts/{account_id}/risk Deletes risk limits for a trading account in the caller's organization. Requires Controls Basic or Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Get account risk control Source: https://docs.anthid.com/api-reference/account-risk-controls/get-account-risk-control /api-reference/controls.openapi.json get /v1/controls/accounts/{account_id}/risk Returns risk limits for a trading account. Users and API keys require Controls Basic or Premium and control read access. Services need `control:read` and `x-organization-id`. The organization must own the account. # Patch account risk control Source: https://docs.anthid.com/api-reference/account-risk-controls/patch-account-risk-control /api-reference/controls.openapi.json patch /v1/controls/accounts/{account_id}/risk Creates or patches risk limits for a trading account. Omitted and null fields are unchanged; `clear` removes named fields. Requires Controls Basic or Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Upsert account risk control Source: https://docs.anthid.com/api-reference/account-risk-controls/upsert-account-risk-control /api-reference/controls.openapi.json put /v1/controls/accounts/{account_id}/risk Creates or replaces risk limits for a trading account in the caller's organization. Omitted optional fields are cleared. Requires Controls Basic or Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Delete account time control Source: https://docs.anthid.com/api-reference/account-time-controls/delete-account-time-control /api-reference/controls.openapi.json delete /v1/controls/accounts/{account_id}/time Deletes time-window controls for a trading account in the caller's organization. Requires Controls Basic or Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Get account time control Source: https://docs.anthid.com/api-reference/account-time-controls/get-account-time-control /api-reference/controls.openapi.json get /v1/controls/accounts/{account_id}/time Returns time-window controls for a trading account. Users and API keys require Controls Basic or Premium and control read access. Services need `control:read` and `x-organization-id`. The organization must own the account. # Patch account time control Source: https://docs.anthid.com/api-reference/account-time-controls/patch-account-time-control /api-reference/controls.openapi.json patch /v1/controls/accounts/{account_id}/time Creates or patches time-window controls for a trading account. Omitted and null fields are unchanged; `clear` removes named fields. Requires Controls Basic or Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Upsert account time control Source: https://docs.anthid.com/api-reference/account-time-controls/upsert-account-time-control /api-reference/controls.openapi.json put /v1/controls/accounts/{account_id}/time Creates or replaces time-window controls for a trading account in the caller's organization. Omitted optional fields are cleared. Requires Controls Basic or Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Create a trading account Source: https://docs.anthid.com/api-reference/accounts/create-a-trading-account /api-reference/accounts.openapi.json post /v1/accounts Creates a trading account and securely stores its broker credentials. An optional `commission_per_share` beside the credential sets the broker's commission per share, in dollars, and an optional `minimum_commission` sets its minimum commission per order, in dollars. Both are nonnegative decimal strings; zero is an explicitly configured zero. Omitted or null leaves the value unset. The caller must be an organization admin. A credential naming a live environment requires the live-trading entitlement, for every broker alike. Organization broker-connection limits apply. # Delete a trading account Source: https://docs.anthid.com/api-reference/accounts/delete-a-trading-account /api-reference/accounts.openapi.json delete /v1/accounts/{account_id} Deletes a trading account and its credentials, then queues removal of the stored secrets. The account stops appearing in reads immediately. The call is idempotent: deleting an already-deleted account succeeds without repeating the work. The caller must be an organization admin. # Get a trading account Source: https://docs.anthid.com/api-reference/accounts/get-a-trading-account /api-reference/accounts.openapi.json get /v1/accounts/{account_id} Returns trading-account metadata, including disabled accounts and simulated accounts. Users and API keys can read their organization's accounts. Service tokens require `credential:read`. # List simulated test accounts Source: https://docs.anthid.com/api-reference/accounts/list-simulated-test-accounts /api-reference/accounts.openapi.json get /v1/accounts/test Lists up to 1,000 non-deleted simulated accounts, newest first, without pagination. Service tokens with `credential:read` may read any organization or omit the filter to read all. Admins of configured internal organizations may read only their own organization. # List trading accounts Source: https://docs.anthid.com/api-reference/accounts/list-trading-accounts /api-reference/accounts.openapi.json get /v1/accounts Lists trading accounts visible to the caller, newest first. Users and API keys are limited to their organization and never see deleted accounts: they may pass their own `organization_id` or omit it, and naming another organization is refused. Service tokens with `credential:read` may list all organizations or filter by `organization_id`, and may include deleted accounts. Pass `account_id` to narrow the listing to one account. Each normal page contains up to 100 accounts. Service tokens may set test_accounts=true for a simulated-account projection of up to 1,000 accounts without pagination, with simulations metadata; user and API-key callers ignore that flag. # Trading Accounts API Source: https://docs.anthid.com/api-reference/accounts/overview Manage broker-connected accounts, their enabled state, and encrypted broker credentials. ## Availability Trading accounts and broker credentials are available on all plans. | Capability | Free | Starter | Professional | | - | - | - | - | | Paper Trading Accounts | ✓ | ✓ | ✓ | | Live Broker Accounts | ✗ | ✓ | ✓ | Paper trading accounts can be used to test integrations and trading workflows without market exposure. Live broker account connectivity is available on Starter and Professional plans only. The organization's billing tier also caps the number of broker connections. Create requests are rejected after that limit is reached. ## Intended purpose Trading accounts represent broker accounts connected to Anthid. They provide the primary boundary for order routing, position management, connection monitoring, and trading controls. Each trading account is associated with one or more broker credentials that allow Anthid to establish and maintain connectivity with an external broker. A trading account acts as the parent resource for most trading-related APIs. ## Access User and API-key callers list trading accounts and credentials in their principal organization. Service-account callers with `credential:read` can list and read across organizations, except on `GET /v1/accounts/credentials`, where they must name an `account_id`. > [!WARNING] > > **Only a signed-in organization admin can create, update, or delete a trading account or broker credential.** These operations take a user bearer token. An organization API key is refused, and so is a service account. Reads accept an API key as normal. Paper credentials are allowed for admins; live credentials additionally require the Trading Live entitlement, for every broker alike. Decrypted credential access is limited to service-account callers with the `credential:decrypt` scope. Use metadata endpoints for normal application workflows. ## Account state Every account has an `enabled` boolean. Disabling an account preserves its metadata and credentials, so an administrator can pause it without deleting the broker connection. Re-enable it with the same endpoint when it is ready to return to service. Both `GET /v1/accounts` and `GET /v1/accounts/{account_id}` return enabled and disabled accounts. Use `enabled=true` or `enabled=false` when listing accounts to select one state. ```bash theme={null} curl "https://api.anthid.com/v1/accounts?enabled=false" \ -H "Authorization: Bearer " ``` To enable or disable an account, send the field you want to change: ```bash theme={null} curl https://api.anthid.com/v1/accounts/ \ -X PATCH \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"enabled": false}' ``` The update endpoint returns the updated account object. ## Commission Accounts expose `commission_per_share` as a decimal string in dollars, such as `"0.0035"`. A null rate means it has not been configured, not that trading is free. `"0"` records an explicit zero rate. Negative rates return `400`. Accounts also expose `minimum_commission`, the broker's minimum charge per order in dollars, such as `"0.10"`. An order is charged the larger of the minimum and its per-share commission. Null, zero and negative values behave as they do for `commission_per_share`. Set both beside the broker credential when creating an account: ```json theme={null} { "Lightspeed": { "account_id": "LS-123456", "environment": "paper", "api_key": "" }, "commission_per_share": "0.0035", "minimum_commission": "0.10" } ``` `PATCH /v1/accounts/{account_id}` accepts any of `enabled`, `commission_per_share` and `minimum_commission`. Omitted or null fields keep their current values. Sending no effective field returns `400`; null cannot clear a stored commission or minimum. The response contains the updated account. ```bash theme={null} curl https://api.anthid.com/v1/accounts/ \ -X PATCH \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"commission_per_share": "0.0035", "minimum_commission": "0.10"}' ``` ## Accounts Accounts represent individual broker accounts managed by Anthid. Each trading account contains: * Broker configuration * An enabled state * An optional commission per share and minimum commission per order * Connection state * Broker credentials * Orders * Positions * Controls Trading accounts are uniquely scoped to an organization. ## Credentials Credentials are broker-specific authentication secrets used to establish connectivity with external broker systems. Credential payloads are stored as encrypted WorkOS Vault objects. Credential APIs allow clients to: * List credential metadata for a trading account * Retrieve credential metadata * Update credential configuration * Rotate credentials * Remove unused credentials Credential metadata does not decrypt or return the underlying secret payload. Credential values should be treated as sensitive secrets and are only available through the service-account-only decrypted credential endpoint. ## Credential variants The OpenAPI schema defines broker credential payload variants for: | Broker | Environments | Integration stability | | - | - | - | | Lightspeed | `paper`, `live` | Beta | The Lightspeed integration is in beta: usable in production, still being hardened, and carrying no service level commitment. See [Integration stability](/pages/brokers/overview#integration-stability) and [Service levels](/pages/brokers/overview#service-levels). ## Endpoint groups | Group | Endpoints | | - | - | | Accounts | `GET` and `POST /v1/accounts`; `GET`, `PATCH`, and `DELETE /v1/accounts/{account_id}` | | Broker credentials | `GET /v1/accounts/credentials`; `GET /v1/accounts/{account_id}/credentials`; `GET`, `PATCH`, and `DELETE /v1/accounts/{account_id}/credentials/{credential_id}` | | Service credential access | `GET /v1/accounts/{account_id}/credentials/{credential_id}/decrypted`; `GET /v1/accounts/{account_id}/credentials/{credential_id}/organization` | | Test accounts | `GET` and `POST /v1/accounts/test`; `PATCH` and `DELETE /v1/accounts/test/{trading_account_id}` | | Operational health | `GET /v1/accounts/outbox/health`; `GET /v1/accounts/runners/health` | List endpoints support cursor-based pagination and return up to 100 records per page. Pass the response's `next_cursor` value back as `cursor` to request the next page. The test account listing is the exception and is unpaginated. Account listing supports: | Parameter | Effect | | - | - | | `enabled` | Select enabled or disabled accounts | | `require_credentials` | When true, return only accounts with an enabled credential | | `account_id` | Narrow to one account. An account the caller cannot see matches nothing and returns an empty page rather than a `403` | | `organization_id` | A user or API key may only name its own; a service token may name any | | `test_accounts` | Service tokens only. When true, returns only simulated accounts, without pagination. Organization, account and enabled filters still apply; credential, deleted-account and cursor filters are ignored. Ignored for users and API keys | | `include_deleted` | Service tokens only. Lists deleted accounts alongside live ones, and is ignored for users and API keys | `include_deleted` exists for readers that have to resolve an account id that history refers to but the organization no longer holds. A deleted account keeps its records, so its broker and environment stay answerable long after it stops being listable. ## Listing credentials across an organization `GET /v1/accounts/credentials` returns credential metadata for the whole organization in one call, without walking accounts. The organization comes from the authenticated principal and cannot be named in the request. `account_id` narrows the listing inside it; it does not redirect it. An account the caller does not own matches nothing and returns an empty page. > [!NOTE] > > **A service token must pass `account_id` here.** A service has no organization of its own to scope the read to, so omitting it returns `400`. Service tokens also need `credential:read`. ## Test accounts Test accounts are simulated trading accounts registered for load testing. The platform answers their orders from an in-process simulator rather than a broker, so their fills are produced locally and reach no venue. Test accounts are always paper, and they are not trading accounts with a flag set. They are a separate resource with no broker, no credential, and no retention obligation, so the default `GET /v1/accounts` listing contains only real accounts. A service token with `credential:read` may explicitly request `test_accounts=true` for an unpaginated projection of simulated accounts with `simulations` metadata. Organization, account and enabled filters still apply; credential, deleted-account and cursor filters are ignored. Users and API keys cannot enable it. Use `GET /v1/accounts/test` for the operator listing and its account, organization and enabled filters. > [!NOTE] > > These endpoints are for operators, not applications. Customers and organization API keys are refused. ### Who can call these Two callers reach the test account endpoints: a client credential carrying the right scope, and a signed-in organization admin of an organization configured as internal. | Operation | Client credential | Internal organization admin | | - | - | - | | List | `credential:read` | ✓ | | Register, retune, remove | `accounts:admin` | ✓ | A client credential may name any organization, or omit `organization_id` when listing to read every tenant at once. An internal organization admin may name only its own. Naming another organization is refused with a `403` rather than quietly narrowed. The rows written or returned would be the same either way, and only the refusal tells you that what you asked for is not what you got. ### Register an account `POST /v1/accounts/test` names the `organization_id` and the `trading_account_id` to trade under, rather than minting one, so an operator can register an id a fixture already refers to. An id that collides with a real trading account or an existing test account is refused with a `409`: a test row shadowing a customer's account would send their orders to a simulator. Simulator timings are optional and default to a 10ms acknowledgement and a 5ms to 100ms fill delay. `ack_latency_ms` is a per-account rate ceiling as much as a latency, because the submitter waits for each command to return before claiming the account's next one, so 10ms caps the account near 100 commands a second. Set it to 0 to measure the platform rather than the simulator. ### List accounts `GET /v1/accounts/test` returns the registered accounts, newest first. The listing is unpaginated and carries no `next_cursor`, because test accounts are registered by hand for a run and there are tens of them rather than thousands. | Parameter | Effect | | - | - | | `organization_id` | Tenant to list. A client credential may name any, or none to read every tenant at once. An internal organization admin may name only its own | | `account_id` | Narrow to one simulated account. An account the caller cannot see matches nothing and returns an empty list | | `enabled` | Select accounts the scheduler is or is not running | Each row carries the simulator's timings and the `service_id` of the credential that registered it, both of which the account projection behind `GET /v1/accounts` leaves out. ### Retune or disable an account `PATCH /v1/accounts/test/{trading_account_id}` updates a registered account. Every field is optional, and an omitted field keeps its stored value. There is no `environment` field, because a test account is always paper and the table refuses anything else. Retuning an account that is running restarts it. The scheduler notices that `updated_at` moved, stops the client, and starts a fresh one with the new timings, which is the same path a real account takes when its credential is rotated. Set `enabled` to false to take the account out of service on the scheduler's next sweep while keeping the registration, so you can re-enable it later. ```bash theme={null} curl https://api.anthid.com/v1/accounts/test/ \ -X PATCH \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"enabled": false}' ``` ### Remove an account `DELETE /v1/accounts/test/{trading_account_id}` removes the registration, and the account stops running on the scheduler's next sweep. Orders it already produced are untouched and keep the retention every other order has. What the delete removes is the registration that made the platform simulate the account. The delete writes a tombstone rather than dropping the row, so it is idempotent: a retry returns the same `200`, and `deleted_at` reports the moment of the first deletion. The response names `test_trading_account` in its `resource` field, so a caller handling several delete endpoints branches on that field rather than on which URL it called. A test account in another organization answers `404` rather than `403`, so the endpoint cannot be used to probe which ids are registered elsewhere. ## Operational health Two read-only endpoints report whether the machinery behind accounts is keeping up. Both are for operators, and both accept the same two callers: a client credential holding the `credential:read` scope, and an organization admin of an organization configured as internal. API keys and every other organization are refused. ### Outbox `GET /v1/accounts/outbox/health` reports how much work the accounts service's transactional outbox is holding, broken down by status and event type, with the age of the oldest row in each state. The response carries no organization id, account id, payload, vault object, or error text. It says how much work is stuck and what kind, not whose, which is what makes a queue-wide read safe to serve. | Signal | What it means | | - | - | | `failed` above zero | Terminal. A row reaches `failed` only after the attempt limit and is never retried, so this is work the platform has dropped and it needs an operator | | `oldest_pending_at` minutes old | A healthy queue drains in under a second, so a pending row this old means the worker is behind or gone | | `oldest_claim_at` older than a few seconds | A `processing` row is reclaimed after five seconds, so an older claim means no worker is running to reclaim it | `entries` is the raw grouping by status and event type. The `pending`, `processing`, `completed`, and `failed` totals are derived from it, so you can read the severity without doing the arithmetic and the breakdown without a second request. ### Runners `GET /v1/accounts/runners/health` performs, once and on demand, the comparison the scheduler makes every second: what should be running, against what the connection bucket says is. `observed` counts every tenant's runners by state and carries no tenant. `test_accounts` lists the caller's simulated accounts with the bucket's view of each. `missing` and `stranded` count the two ways those disagree: an enabled account nothing is running, and a disabled account something still is. Only simulated accounts are compared. Running a live account additionally requires the organization's live trading entitlement, which this service does not check, so comparing one here would report a billing exclusion as a missing runner. > [!NOTE] > > Bucket entries expire sixty seconds after the last heartbeat, so a state of `absent` means nothing has reported within the minute, not that no runner exists. Measure staleness against the response's `checked_at`, which is when the bucket was read, rather than against your own clock. ## Common use cases * Connect a new broker account * Rotate broker API credentials * Decommission broker accounts * Manage multiple trading environments * Separate paper and live trading accounts * Organize accounts by strategy or broker ## Related Resources * [Connections](/api-reference/connections/overview) * [Orders](/pages/concepts/orders) * [Positions](/pages/concepts/positions) * [Controls](/pages/controls/overview) * [Organizations](/api-reference/organizations/overview) ## Support For questions about authentication, request formats, or API behavior, contact [support@anthid.com](mailto:support@anthid.com). # Register a simulated test account Source: https://docs.anthid.com/api-reference/accounts/register-a-simulated-test-account /api-reference/accounts.openapi.json post /v1/accounts/test Registers a paper account whose orders are filled by a simulator. Service tokens with `accounts:admin` may name any organization. Admins of configured internal organizations may register accounts only in their own organization. API keys are refused. # Remove a test account Source: https://docs.anthid.com/api-reference/accounts/remove-a-test-account /api-reference/accounts.openapi.json delete /v1/accounts/test/{trading_account_id} Soft-deletes a simulated account and stops it on the next scheduler sweep. Existing orders are retained. Service tokens with `accounts:admin` may delete any organization's accounts. Admins of configured internal organizations may delete only their own accounts. # Report the transactional outbox's health Source: https://docs.anthid.com/api-reference/accounts/report-the-transactional-outboxs-health /api-reference/accounts.openapi.json get /v1/accounts/outbox/health Reports queue counts and oldest timestamps by status and event type across all organizations, without tenant identifiers or payloads. Failed rows are no longer retried. Requires a service token with `credential:read` or an admin of a configured internal organization. # Report which simulated accounts are actually running Source: https://docs.anthid.com/api-reference/accounts/report-which-simulated-accounts-are-actually-running /api-reference/accounts.openapi.json get /v1/accounts/runners/health Compares simulated accounts with current runner heartbeats. `observed` contains counts across all organizations; `test_accounts` contains accounts in the caller's scope. Heartbeats expire after 60 seconds. Requires a service token with `credential:read` for all organizations, or an admin of a configured internal organization for its accounts. # Retune or disable a test account Source: https://docs.anthid.com/api-reference/accounts/retune-or-disable-a-test-account /api-reference/accounts.openapi.json patch /v1/accounts/test/{trading_account_id} Updates a simulated account. Omitted or null fields keep their values. Disabling stops the account on the next scheduler sweep; retuning restarts a running account. Service tokens with `accounts:admin` may update any organization. Admins of configured internal organizations may update only their own accounts. # Update a trading account Source: https://docs.anthid.com/api-reference/accounts/update-a-trading-account /api-reference/accounts.openapi.json patch /v1/accounts/{account_id} Updates the account's enabled state or commission settings. Commission values are nonnegative decimal strings in dollars. Omitted or null fields keep their values; zero sets an explicit zero. At least one non-null field is required. Only organization admins may update accounts. # Create a billing portal session Source: https://docs.anthid.com/api-reference/billing/create-a-billing-portal-session /api-reference/billing.openapi.json post /v1/billing/organizations/{org_id}/portal-session Creates a Stripe billing portal session and returns its URL. Requires a user who is an admin of the organization in the path. # Get billing claims Source: https://docs.anthid.com/api-reference/billing/get-billing-claims /api-reference/billing.openapi.json get /v1/billing/organizations/{org_id}/claims Returns the entitlements and usage limits for an organization. Users and API keys can read their own organization. Service tokens require `billing:read`. # Delete a broker credential Source: https://docs.anthid.com/api-reference/broker-credentials/delete-a-broker-credential /api-reference/accounts.openapi.json delete /v1/accounts/{account_id}/credentials/{credential_id} Disables a broker credential and its trading account, then queues removal of the stored secret. The credential must belong to the account in the path. The caller must be an organization admin. # Get a broker credential Source: https://docs.anthid.com/api-reference/broker-credentials/get-a-broker-credential /api-reference/accounts.openapi.json get /v1/accounts/{account_id}/credentials/{credential_id} Returns credential metadata without returning the secret value. The credential must belong to the account in the path. Users and API keys can read credentials in their organization. Service tokens require `credential:read`. # Get a credential's organization Source: https://docs.anthid.com/api-reference/broker-credentials/get-a-credentials-organization /api-reference/accounts.openapi.json get /v1/accounts/{account_id}/credentials/{credential_id}/organization Returns the organization that owns a broker credential. The credential must belong to the account in the path. Only service tokens with `credential:read` may call this endpoint. # Get a decrypted broker credential Source: https://docs.anthid.com/api-reference/broker-credentials/get-a-decrypted-broker-credential /api-reference/accounts.openapi.json get /v1/accounts/{account_id}/credentials/{credential_id}/decrypted Returns the broker's secret credential payload. The credential must belong to the account in the path. This sensitive endpoint is limited to service tokens with `credential:decrypt`. # List broker credentials Source: https://docs.anthid.com/api-reference/broker-credentials/list-broker-credentials /api-reference/accounts.openapi.json get /v1/accounts/{account_id}/credentials Lists up to 100 credentials for the account without secret values. Users and API keys must own the account through their organization. Service tokens with `credential:read` may list any account. Use `GET /v1/accounts/credentials` to list across an organization. # List broker credentials for an organization Source: https://docs.anthid.com/api-reference/broker-credentials/list-broker-credentials-for-an-organization /api-reference/accounts.openapi.json get /v1/accounts/credentials Lists credential metadata across the caller's organization without returning secret values. The organization is taken from the authenticated principal and cannot be named in the request. Pass `account_id` to narrow the listing to one trading account; an account the caller does not own matches nothing and returns an empty page. Service tokens require `credential:read` and must pass `account_id`, because a service has no organization of its own to scope the read to. Each page contains up to 100 credentials. # Update a broker credential Source: https://docs.anthid.com/api-reference/broker-credentials/update-a-broker-credential /api-reference/accounts.openapi.json patch /v1/accounts/{account_id}/credentials/{credential_id} Replaces the secret value for a broker credential. The credential must belong to the account in the path. The caller must be an organization admin. A replacement naming a live environment requires the live-trading entitlement, for every broker alike. # Get a connection status Source: https://docs.anthid.com/api-reference/connections/get-a-connection-status /api-reference/connections.openapi.json get /v1/connections/status/{account_id} Returns the account's active or pending connection status. Requires intent read permission for its environment. Users and API keys may read only their organization; service tokens may read across organizations. # List connection statuses Source: https://docs.anthid.com/api-reference/connections/list-connection-statuses /api-reference/connections.openapi.json get /v1/connections/status Lists real and simulated account statuses in environments the caller may read. Users and API keys are scoped to their organization; service tokens may read across organizations. Simulated accounts are omitted if their listing fails. Requires intent read permission in at least one environment. # Connections API Source: https://docs.anthid.com/api-reference/connections/overview Query broker connection statuses last seen by the platform. When a trading account is connected to a broker, Anthid establishes a persistent worker connection that maintains authentication, monitors account health, and synchronizes broker state with the platform. This worker is represented by a trading account connection status. Connection APIs provide visibility into the worker's current status, allowing clients to determine whether the broker is reachable and the integration is operating normally. ## Availability Available on all plans. ## Intended Purpose Use connection APIs to: * List connection statuses for accounts visible to the caller * Verify broker connectivity for a single trading account * Monitor active and pending connection health * Troubleshoot broker integration issues The caller must have intent read authorization. User and API-key callers list accounts in their principal organization; service-account callers may list accounts visible to the service credentials. Simulated accounts are excluded. The list can omit accounts whose status lookup fails, so an absent entry does not prove the account is disconnected. ## Endpoints | Endpoint | Purpose | | - | - | | `GET /v1/connections/status` | List statuses for broker connection accounts visible to the caller | | `GET /v1/connections/status/{account_id}` | Get the current status for one broker connection account | ## Status response Each status includes the account ID, current state, and two timestamps: ```json theme={null} { "trading_account_id": "550e8400-e29b-41d4-a716-446655440000", "status": "Ok", "last_edit_at": "2026-06-03T11:55:00Z", "last_update_at": "2026-06-03T12:00:00Z" } ``` | Field | Meaning | | - | - | | `last_edit_at` | When the platform last changed the account's connection configuration or desired lifecycle state | | `last_update_at` | When the connection worker last reported its observed status | An error state is represented as an object, for example `"status": {"Error": "authentication failed"}`. Other states are strings. ## Connection lifecycle 1. Create a trading account 2. Configure broker credentials 3. Establish broker connectivity 4. Monitor connection status 5. Execute trading activity ## Connection states A connection may report: | State | Description | | - | - | | `Pending` | The connection is being established or has not reached an active state | | `Ok` | Broker communication is healthy | | `Error` | The platform encountered a broker integration error and returned an error message | | `Unknown` | No active or pending status is available | ## Polling considerations Connection status represents the most recent state observed by the platform. Compare `last_edit_at` with `last_update_at` when deciding whether a status reflects the most recent account change. Status information may lag broker-side events depending on broker update frequency and network conditions. ## Common use cases * Display broker health in dashboards * Verify readiness before submitting orders * Alert operators to broker outages * Audit connection stability ## Related Resources * [Trading Accounts](/api-reference/accounts/overview) * [Credentials](/api-reference/accounts/overview#credentials) * [Orders](/pages/concepts/orders) * [Positions](/pages/concepts/positions) ## Support For questions about authentication, request formats, or API behavior, contact [support@anthid.com](mailto:support@anthid.com). # Evaluate controls Source: https://docs.anthid.com/api-reference/control-evaluation/evaluate-controls /api-reference/controls.openapi.json post /v1/controls/evaluate Evaluates organization controls for JSON `null`, or effective order controls for an order context with a resolved `instrument`. Non-equity instruments are blocked. Supply `side` when position or daily-loss controls apply; those controls block orders without it. Both allowed and blocked results return 200. Users and API keys require Controls Basic or Premium. Users need an organization role; API keys need control read permission for the account environment, or both environments for `null`. Services need `control:evaluate` and `x-organization-id`. # Controls API Source: https://docs.anthid.com/api-reference/controls/overview Manage and evaluate manual, risk, and time controls across organizations, trading accounts, and symbols. ## Enforcement scope Controls are evaluated when the Intents API accepts a `CREATE` command. `REPLACE` and `CANCEL` do not run that evaluation. A kill switch blocks new creates in its scope; it does not cancel resting orders, flatten positions, or prevent a replacement. Manage existing orders separately. Only maximum order size and the configured manual switches and trading windows are enforced today. Daily-loss limits, maximum position size, and `stale_order_millis` are stored configuration, not active protection. The stale threshold does not reject or automatically cancel an aging order. ## Availability The `controls` entitlement is ordered rather than on or off. It takes one of three values, each admitting everything the one below it admits. | Entitlement | What it admits | | - | - | | `premium` | Everything in `basic`, plus organization-scoped and symbol-scoped controls | | `basic` | Trading account controls (manual, risk, and time), the account control history, and control evaluation | | `none` | Nothing. Every controls endpoint returns `403` | `basic` ships with any plan that can trade live, so account-level limits and the account kill switch are not reserved for the top plan. Organization-wide and per-symbol controls require `premium`. Read your organization's current entitlement from `GET /v1/billing/organizations/{org_id}/claims`. > [!NOTE] > > Compare the entitlement as an ordering, not for equality. A caller holding `premium` also satisfies every check that asks for `basic`; testing for `controls == "basic"` refuses the plan that pays for more. ## Intended Purpose Controls allow organizations to define restrictions that govern trading activity before orders are submitted to a broker. Anthid evaluates configured controls in real time and can prevent trading activity that violates organizational policies, risk limits, or trading schedules. Controls can be applied at multiple levels: * Organization Controls apply across an entire organization. * Trading Account Controls apply to a specific trading account. * Symbol Controls apply only to a specific symbol within a trading account. ## Access Most read operations use the caller's principal organization. Service-account callers must provide `x-organization-id` where supported, and the requested organization must own the trading account. Every operation is checked twice: once against the entitlement above, and once against the caller's permission. Editing requires organization control edit access, meaning the organization Admin role for a user caller, or control edit permission on an API key. A key's permission is granted per environment and checked against the account's: `paper:control:edit` covers controls on paper accounts only. Organization controls apply to every account, so editing them needs both `paper:control:edit` and `live:control:edit`. Reading works the same way with `paper:control:read` and `live:control:read`, and is available to more user roles. The entitlement each scope asks for: | Scope | Read | Edit | | - | - | - | | Trading account (manual, risk, time, history) | `basic` | `basic` | | Symbol (manual, risk, time, effective listing) | `premium` | `premium` | | Organization | `premium` | `premium` | | Evaluation | `basic` | Not applicable | Symbol controls are gated the same way for reading as for editing: a plan that cannot set one has none to read, so admitting the read would only return an empty answer while implying the surface was available. Internal service tokens are authorized by scope alone and carry no entitlement. `control:edit` authorizes edits, `control:evaluate` authorizes evaluation, and the evaluation path itself runs as a service so that controls still apply to orders on every plan. > [!NOTE] > > **A `403` does not distinguish between its causes, deliberately.** Missing entitlement, missing permission, and naming a trading account owned by another organization all return the same status, because separating them would confirm to a caller which accounts exist. Check entitlements and role before assuming a bug in the request. `GET /v1/controls/accounts/{account_id}/symbols` joins the stored manual, risk, and time records at symbol scope. It does not merge organization or account controls into each returned symbol. ## Control Hierarchy Controls are evaluated at three levels. More specific controls apply to a narrower scope of trading activity. ```text theme={null} Organization │ ├── Organization Controls │ └── Trading Account │ ├── Account Controls │ └── Symbol │ └── Symbol Controls ``` ## Control Types ### Manual Controls Manual controls provide a simple mechanism for enabling or disabling trading activity. Common use cases include: * Emergency trading halts * Maintenance windows * Compliance reviews * Temporary account restrictions ### Risk Controls Risk controls enforce quantitative trading limits. Examples include: * Maximum daily loss amounts * Maximum daily loss percentages * Maximum position sizes * Maximum order sizes Risk controls help ensure trading activity remains within predefined risk tolerances. Risk quantities and percentages are serialized as decimal strings, not JSON numbers. This preserves decimal precision across clients. Every risk limit is optional and may be `null`. ```json theme={null} { "max_daily_loss_amount": "2500.00", "max_daily_loss_percent": "2.5", "max_position_size": "1000", "max_order_size": "100" } ``` ### Time Controls Time controls restrict when trading activity is permitted. `trading_start` and `trading_end` are times of day written as `HH:MM:SS`. `stale_order_millis` is an integer. All three are optional and may be `null`. ```json theme={null} { "trading_start": "13:30:00", "trading_end": "20:00:00", "stale_order_millis": 30000 } ``` > [!WARNING] > > **The window is evaluated in UTC, not in the account's market timezone.** An order is refused when the current UTC time of day falls before `trading_start` or after `trading_end`. > > Regular US market hours are 09:30 to 16:00 Eastern. In UTC that is `13:30:00` to `20:00:00` while Eastern Daylight Time is in effect, and `14:30:00` to `21:00:00` while Eastern Standard Time is. The example above covers the daylight-saving half of the year. > > Anthid does not shift the window when the offset changes, so a window pinned to market hours has to be updated twice a year. > > A window that crosses midnight UTC cannot be expressed. A `trading_start` later than `trading_end` refuses everything, because both conditions are true at once. Time controls are commonly used to enforce market-hour restrictions and operational trading windows. ## Endpoint Groups Every control path serves `GET`, `PUT`, `PATCH`, and `DELETE`. | Group | Endpoints | | - | - | | Organization manual controls | `GET`, `PUT`, `PATCH`, and `DELETE /v1/controls/organizations`; service-account read by id at `GET /v1/controls/organizations/{org_id}` | | Account manual controls | `GET`, `PUT`, `PATCH`, and `DELETE /v1/controls/accounts/{account_id}` | | Account control history | `GET /v1/controls/accounts/{account_id}/history` | | Account time controls | `GET`, `PUT`, `PATCH`, and `DELETE /v1/controls/accounts/{account_id}/time` | | Account risk controls | `GET`, `PUT`, `PATCH`, and `DELETE /v1/controls/accounts/{account_id}/risk` | | Effective symbol controls | `GET /v1/controls/accounts/{account_id}/symbols` | | Symbol manual controls | `GET`, `PUT`, `PATCH`, and `DELETE /v1/controls/accounts/{account_id}/symbols/{symbol}` | | Symbol time controls | `GET`, `PUT`, `PATCH`, and `DELETE /v1/controls/accounts/{account_id}/symbols/{symbol}/time` | | Symbol risk controls | `GET`, `PUT`, `PATCH`, and `DELETE /v1/controls/accounts/{account_id}/symbols/{symbol}/risk` | | Evaluation | `POST /v1/controls/evaluate` | `GET /v1/controls/accounts/{account_id}/symbols` is cursor-paginated. When its response contains a non-null `next_cursor`, pass that value in the next request's `next_cursor` query parameter. ## Replacing and amending controls `PUT` replaces the whole control. Any field the body omits is reset. `PATCH` changes only the fields it names, and it does not use `null` to remove one. A `null` in a `PATCH` body is ignored, so a field is removed by naming it in the `clear` array instead. ```json theme={null} { "max_order_size": "250", "clear": ["max_daily_loss_percent"] } ``` That request raises the order size limit, drops the daily loss percentage, and leaves every other limit as it was. `clear` accepts only the field names belonging to that control type, so a misspelled name is rejected rather than silently doing nothing. A caller that believes it re-enabled an account and did not is the failure this prevents. | Control type | Clearable fields | | - | - | | Manual | `manually_disabled`, `manual_reason` | | Risk | `max_daily_loss_amount`, `max_daily_loss_percent`, `max_position_size`, `max_order_size` | | Time | `trading_start`, `trading_end`, `stale_order_millis` | Clearing `manually_disabled` restores its default of `false`, because the column cannot be null. ## Control Evaluation When an order or trading action is evaluated, Anthid checks the effective organization, account, and symbol controls and determines whether the action should be permitted. If one or more controls are triggered, the evaluation response identifies: * The control scope * The control type * The reason the action was rejected The response includes an `allowed` boolean and a `triggered` list. Pass `null` to evaluate organization-level controls only, or pass an account id, symbol, and quantity to include account-level and symbol-level checks. This allows applications to present meaningful feedback to users and operators. ## Common Use Cases * Disable trading during incidents or maintenance * Restrict trading outside approved hours * Enforce account-level risk limits * Apply symbol-specific trading restrictions * Implement organization-wide trading policies * Build automated compliance workflows ## Related Resources * [Trading Accounts](/api-reference/accounts/overview) * [Orders](/pages/concepts/orders) * [Positions](/pages/concepts/positions) * [Connections](/api-reference/connections/overview) * [Control Evaluation](/api-reference/controls/overview#control-evaluation) ## Support For questions about authentication, request formats, or API behavior, contact [support@anthid.com](mailto:support@anthid.com). ## Control history pagination `GET /v1/controls/accounts/{account_id}/history` returns newest changes first, ordered by timestamp and event ID. Pass its opaque `next_cursor` as the next request's `cursor`. The limit defaults to 25 and is clamped to 1 through 100. Stop when `next_cursor` is null. Timestamp-only cursors from the earlier implementation return `400` with a restart message. Restart without a cursor; those older cursors lack the tie breaker needed to resume reliably. # List effective symbol controls Source: https://docs.anthid.com/api-reference/effective-controls/list-effective-symbol-controls /api-reference/controls.openapi.json get /v1/controls/accounts/{account_id}/symbols Lists symbol-level manual, risk, and time controls, ordered by symbol in pages of 25. Does not merge organization or account controls. Users and API keys require Controls Premium and account control read access; services need `control:read` and `x-organization-id`. # Get instrument Source: https://docs.anthid.com/api-reference/instruments/get-instrument /api-reference/instruments.openapi.json get /v1/instruments/{instrument_id} Returns one instrument by its stable platform ID, regardless of catalogue status. No asset class is required. Available to authenticated users, API keys and services. # List instruments Source: https://docs.anthid.com/api-reference/instruments/list-instruments /api-reference/instruments.openapi.json get /v1/instruments Lists the shared equity and option catalogue. Defaults to ACTIVE entries; catalogue status does not grant trading permission. Each `instrument` is tagged by `asset_class`. Text filters are trimmed and blank values are ignored. Continue with `next_cursor` until null, keeping filters and sort direction unchanged. # List short sale borrow rates Source: https://docs.anthid.com/api-reference/instruments/list-short-sale-borrow-rates /api-reference/instruments.openapi.json get /v1/instruments/short-rates Lists overnight borrow rates from each broker's latest loaded list. Either date parameter selects history; both bounds include the named New York calendar day, and an omitted bound is open. Rates are dollars per share per night. Null means no premium was stated; no row means the symbol was not listed. Results sort by symbol, broker and time. Continue with `next_cursor` until null. # Instruments API Source: https://docs.anthid.com/api-reference/instruments/overview Look up instrument identities and contract details. The Instruments API provides a shared catalog of instrument identities and contract details. It accepts organization API keys, user bearer tokens, and service bearer tokens. Every authenticated caller reads the same catalog; no organization or `environment` filter is required. Catalog coverage does not imply that a connected broker supports trading every listed asset class or venue. Check [broker capabilities](/pages/brokers/overview) before submitting an intent. ## Endpoints | Method and path | Returns | | - | - | | `GET /v1/instruments` | A page of instrument records | | `GET /v1/instruments/{instrument_id}` | One record by its stable platform UUID | | `GET /v1/instruments/short-rates` | Current or historical overnight borrow rates | ```bash theme={null} curl "https://api.anthid.com/v1/instruments?asset_class=EQUITY&symbol=AAPL&limit=50" \ -H "x-api-key: " ``` ## Filtering and pagination Listings default to `status=ACTIVE`, `sort_direction=Asc`, and `limit=50`. Limits are clamped to 1 through 500. Other status values are `INACTIVE`, `EXPIRED`, and `DELISTED`. Asset classes and statuses use uppercase wire values. Use `asset_class`, `venue`, and `symbol` for an exact instrument lookup. The catalog supports `EQUITY` and `OPTION`; other asset classes return `400`. Use `underlying` to narrow options. Text filters are trimmed; blank values are ignored. Symbols and venues retain their own spelling. `expires_after` is an inclusive RFC 3339 lower bound, and `expires_before` is an exclusive upper bound. Instruments without an expiry do not match either bound. A lower bound at or after the upper bound returns `400`. The response contains `data` and `next_cursor`. Pass a non-null cursor unchanged with the same filters and sort direction. Stop when it is null. Lowercase `asc` and `desc` are also accepted. ## Instrument identity An `InstrumentRecord` contains `instrument_id`, `status`, `instrument`, `created_at`, `updated_at`, and `last_seen_at`. The UUID is stable across symbol renames. `last_seen_at` records the last catalog refresh that confirmed the instrument. The nested `instrument` is tagged by `asset_class`. Its fields depend on that class, such as an equity's exchange or an option's strike and expiration. Prices and quantities use decimal strings. Look up instruments through the listing filters or their platform UUID. An unknown instrument ID returns `404`. An optional `instrument` on a broker event carries resolved metadata. An absent value means unknown metadata, not an equity assumption. The order's `symbol` remains its routing spelling. ## Short sale borrow rates Without dates, `GET /v1/instruments/short-rates` returns each broker's latest list. Filter by `broker` or `symbol`. Supply `start_date`, `end_date`, or both to read history, with inclusive dates on the New York calendar. `rate_per_share` is a decimal string in dollars per share per night, not an annual percentage. A null rate means the broker lists the symbol without stating a premium. No row means the symbol is absent from that broker's list. Rows sort by symbol, broker, then time. `limit` defaults to 1000 and is clamped to 1 through 10000. Pass `next_cursor` unchanged with the same filters until it is null. # Get latest intent action Source: https://docs.anthid.com/api-reference/intents/get-latest-intent-action /api-reference/intents.openapi.json get /v1/intents/{intent_id} Returns the latest action recorded for an intent. An optional trading account ID narrows the lookup to that account. The response includes the submitted `symbol`, the resolved `instrument`, dispatch `state`, and the separate broker `runtime_status`. A null runtime status means the broker has not reported one. # List intent actions Source: https://docs.anthid.com/api-reference/intents/list-intent-actions /api-reference/intents.openapi.json get /v1/intents/{intent_id}/actions Lists an intent's append-only action history. Results are newest first by default and can be filtered by account or symbol. Each action includes the resolved `instrument`, dispatch `state`, and broker `runtime_status`. # List refused submissions Source: https://docs.anthid.com/api-reference/intents/list-refused-submissions /api-reference/intents.openapi.json get /v1/intents/rejections Lists submissions refused by authorization, entitlement, or trading controls, newest first within one environment. Filter by account, refusal stage, or trailing days. # Intents API Source: https://docs.anthid.com/api-reference/intents/overview Submit, inspect, and measure broker-agnostic trading intents. ## Availability Intent management is available on all plans. ## Intended Purpose Intents provide a broker-agnostic interface for submitting trading actions through Anthid. Instead of integrating directly with broker-specific order entry APIs, applications submit intents describing the desired trading action. Anthid validates, routes, tracks, and executes those requests through the appropriate broker integration while exposing a consistent platform interface. This allows applications to integrate once and operate across supported brokers without implementing broker-specific order submission logic. ## Access Submitting intents is authorized for API-key callers holding intent edit permission for the account's environment (`paper:intent:edit` or `live:intent:edit`), and for user callers that hold the organization admin role. A user access token is minted for one organization at a time, so the role is read against the organization that token is scoped to. Either way the caller must belong to the organization that owns the named trading account, and an account owned by another organization is refused. Create intents on live accounts additionally require the Trading Live entitlement and must pass controls evaluation before an action and outbox record are written. Read operations require intent read access. Service-account callers must provide `x-organization-id` and have `intent:read`; API-key callers need intent read permission for the environment read (`paper:intent:read` or `live:intent:read`). Listings (`GET /v1/intents` and `GET /v1/intents/rejections`) take a required `environment` query parameter, so a caller holding both is never answered with the two mixed; reading one intent by id needs no environment and is checked against that intent's account; user callers need an organization admin, member, or read-only role. Non-service callers can only read their principal organization. ## Intent Commands The submit endpoint appends an intent command for the trading account named in the body as `trading_account_id`: | Command | Purpose | | - | - | | `CREATE` | Start a new intent lineage for a market, limit, stop, or stop-limit order | | `REPLACE` | Append a replacement action for an existing intent referenced by `ref_intent_id` | | `CANCEL` | Append a cancellation action for an existing intent referenced by `ref_intent_id` | Create commands include a symbol, order side, optional route strategy, and order spec, and may name their own `intent_id`. Replace commands include a replacement order spec. Cancel commands only require the referenced intent id. Quantities and prices are decimal strings. A replacement or cancellation references the original lineage's `intent_id`, not a broker order ID. > [!WARNING] > > **`REPLACE` carries a complete order specification. It is not a patch.** Fields omitted from the replacement spec are not inherited from the original order, so a replace naming only a new `price` will not preserve the original `quantity`. Build the full spec on every replace. ### Create ```json theme={null} { "trading_account_id": "019c0538-1f4a-7a90-8d33-2b7c4e9a51d0", "command": { "type": "CREATE", "payload": { "intent_id": "019c0538-86f2-7c32-9c1e-6f79cbfdb7b4", "symbol": "AAPL", "order_venue": "SMART", "order_side": "BUY", "spec": { "Limit": { "price": "195.00", "quantity": "10", "time_in_force": "DAY" } } } } } ``` ### Replace ```json theme={null} { "trading_account_id": "019c0538-1f4a-7a90-8d33-2b7c4e9a51d0", "command": { "type": "REPLACE", "payload": { "ref_intent_id": "019c0538-86f2-7c32-9c1e-6f79cbfdb7b4", "spec": { "Limit": { "price": "194.50", "quantity": "8", "time_in_force": "DAY" } } } } } ``` ### Cancel ```json theme={null} { "trading_account_id": "019c0538-1f4a-7a90-8d33-2b7c4e9a51d0", "command": { "type": "CANCEL", "payload": { "ref_intent_id": "019c0538-86f2-7c32-9c1e-6f79cbfdb7b4" } } } ``` > [!NOTE] > > `intent_id` on a `CREATE` is optional. Omit it and Anthid mints one and returns it on the response. Name it and Anthid uses yours: mint a UUIDv7 before sending the create, and every event for the order carries an id you already hold, including events that arrive before the response does. An `intent_id` that is not a UUIDv7 is refused with `400`, and one that already exists with `409`. The `REPLACE` and `CANCEL` above name the same id as `ref_intent_id`. ## Request correlation and retries `intent_id` identifies your intent lineage, and it is the id every order, fill, and action under the intent carries. Minting it yourself is what lets you attribute an event on an account-wide stream to the system that placed the order, before that system has heard back from the submission. The optional `Idempotency-Key` header makes a retry safe, for every command. Within an organization a key is spent by the first request that submits under it: | Retry | Result | | - | - | | Same key, same request | `201` with the intent the first request made. Nothing is submitted again. | | Same key, different request | `409` | | No key | Submitted again. A `CREATE` opens another intent unless it names an `intent_id` that already exists, which is `409`. | A request that was refused rather than submitted spends nothing, so the key can be reused once the cause is fixed. Keys are at most 255 bytes. Use a new key for every new command, and the same key when retrying one after an uncertain response. ## Settle an uncertain command If a command is no longer wanted and its submission response is unknown, call `POST /v1/intents/void` with `trading_account_id` and the original `idempotency_key`. You may also supply `intent_id`. `VOIDED` means the key is fenced: later submissions under it return `412`. `WRITTEN` means the command was already recorded and returns its intent ID. Voiding waits for a concurrent write to settle and can be retried. It does not cancel an order that was already submitted. ## Intent state is not fill state An intent read returns two different answers about where things stand, and they are not interchangeable. | Field | Reports | | - | - | | `state` | How far the platform got in dispatching the command: `PENDING`, `PROCESSING`, `SENT`, `FAILED` | | `runtime_status` | What the broker did with the resulting order: `PENDING`, `OPEN`, `PARTIALLY_FILLED`, `FILLED`, `CANCELLED`, `REJECTED` | `state` reaches `SENT` when Anthid has handed the order to the broker and the broker accepted the handoff. It says nothing about execution, and it is the last state the intent takes: dispatch is terminal at `SENT` or `FAILED`, and stays `SENT` forever afterwards, including after the order fills. `runtime_status` is the other half of the answer, and usually the one callers want. It is reported per intent rather than per action, because an intent has one live order at a time and a replace does not give the superseded action its own outcome. Every action in a chain therefore carries the same value. > [!WARNING] > > **`SENT` is not filled.** Treating it as completion is the most common correctness bug written against this API. Read `runtime_status`, or read `filled_quantity` from the resulting order. > [!NOTE] > > `runtime_status` is `null` when the broker has not reported yet. That is deliberately not collapsed into `PENDING`: a null says the platform has heard nothing, where `PENDING` is the broker saying it holds the order and has not worked it. A caller that needs one value can treat null as pending, but a caller watching for a stuck order needs to tell them apart. For the exact quantity traded, read the resulting order through `GET /v1/orders?environment=&intent_id=...` or from the stream. `filled_quantity` is the only field that reports how much actually traded. Note the Ledger requires `environment`, which is `paper` or `live`. See [Orders](/pages/concepts/orders) for the order state model. ## Endpoint groups | Group | Endpoints | | - | - | | Submit and list | `GET` and `POST /v1/intents` | | Settle an uncertain command | `POST /v1/intents/void` | | Latest action | `GET /v1/intents/{intent_id}` | | Action history | `GET /v1/intents/{intent_id}/actions` | | Refused submissions | `GET /v1/intents/rejections` | These endpoints read the operational intent store. Completed intents can remain visible while archival and cleanup finish, so presence in this list does not guarantee an order can still be replaced or cancelled. Inspect `runtime_status` and the resulting order before acting. Completed intents, and the analytics over them, are served by the [Ledger](/api-reference/ledger/overview) at `GET /v1/ledger/intents/history` and `GET /v1/ledger/intents/stats`, where the full instruction chain is retained for six years. Both require an `environment` query parameter of `paper` or `live`, as every Ledger read does. The trading account is not part of the path. Submissions name it in the request body as `trading_account_id`. Reads are scoped to your organization and accept an optional `trading_account_id` query parameter to narrow them to a single account. List endpoints return append-only action records and support `next_cursor`, `limit` and `sort_direction`. `limit` defaults to 100 and is clamped to 1 through 100. To query a period, use the Ledger's history endpoints, which are built for range queries and sort across the whole retained record. ## Submissions that never became intents `GET /v1/intents/rejections` returns what this service refused before an intent existed. It is the counterpart to listing intent actions, which returns what was accepted. | Stage | What it covers | | - | - | | `CONTROLS` | Blocked by the organization's trading controls. This is the supervisory review. | | `AUTHORIZATION` | The caller lacked permission for the account or action | | `ENTITLEMENT` | The plan did not cover what was asked for, such as a live order without the live-trading entitlement | Narrow with `stage` and `trading_account_id`, and bound with a trailing `days` window. Omitting `days` returns the whole retained history, clamped to the six-year retention period. A refusal is recorded with the same detail the caller saw, so a review after the fact answers what was attempted and why it was stopped. See [Compliance](/pages/about/compliance) for how refusals are retained. ## Common Use Cases * Submit market, limit, stop, and stop-limit orders. * Build automated trading systems using a unified API. * Route orders through platform controls and risk checks. * Track order execution through a consistent lifecycle. * Support multiple brokers without maintaining separate integrations. * Create trading workflows that operate across paper and live environments. * Generate auditable records of trading activity. ## Lifecycle An intent represents a requested trading action. After submission, Anthid validates the request, evaluates applicable controls, appends an action record, queues broker execution, and tracks execution status throughout its lifecycle. Applications can monitor intent activity using: * Intent action history * Intent stats * Streaming API * Ledger APIs ## Common Workflow 1. Create a Trading Account. 2. Submit an Intent. 3. Anthid validates and processes the request. 4. The request is routed to the broker. 5. Order updates are streamed through the Streaming API. 6. Historical activity becomes available through the Ledger APIs. ## Related Resources * [Trading Accounts](/api-reference/accounts/overview) * [Brokers](/pages/brokers/overview) * [Orders](/pages/concepts/orders) * [Positions](/pages/concepts/positions) * [Ledger](/api-reference/ledger/overview) * [Streaming API](/api-reference/stream/overview) * [Controls](/pages/controls/overview) ## Support For questions about authentication, request formats, or API behavior, contact [support@anthid.com](mailto:support@anthid.com). # Submit intent Source: https://docs.anthid.com/api-reference/intents/submit-intent /api-reference/intents.openapi.json post /v1/intents Submits a create, replace, or cancel command for an account in the caller's organization. API keys need intent edit permission for the account environment; users need an admin role. Creates require a routable `order_venue`, resolve the symbol's instrument, and pass trading controls. Live creates also require live-trading entitlement. Replace and cancel inherit the instrument and strategy; replace cannot change time in force. Authorization, entitlement, and control refusals are recorded for the rejections endpoint. # Void a command Source: https://docs.anthid.com/api-reference/intents/void-a-command /api-reference/intents.openapi.json post /v1/intents/void Settles an organization-scoped command key. `VOIDED` prevents any later submission under that key; `WRITTEN` returns the intent already written. Waits for concurrent writes and returns the same outcome on retries. API keys need intent edit permission for the account environment; users need an admin role. # API Authentication Source: https://docs.anthid.com/api-reference/introduction Authenticate and send requests to the Anthid APIs Use the Anthid API reference to explore available endpoints, request parameters, response schemas, and errors. ## Authentication Anthid endpoints use one or more of the following credential types: | Credential | Header | Typical use | | - | - | - | | Organization API key | `x-api-key: ` | Intent submission, ledger queries, controls, connections, instruments, and streaming | | User bearer token | `Authorization: Bearer ` | User, organization, account, and administrator workflows | | Client-credentials bearer token | `Authorization: Bearer ` | Service-to-service reads and restricted credential operations | Not every endpoint accepts every credential type. The authentication section on each generated endpoint page is authoritative. Keep credentials private. Do not embed API keys, user tokens, or service tokens in public client-side code. ### REST REST requests use JSON for both request bodies and responses. Include `Content-Type: application/json` when sending a request body. Organization API key example: ```bash theme={null} curl https://api.anthid.com/v1/intents \ -X POST \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "trading_account_id": "", "command": { "type": "CREATE", "payload": { "symbol": "AAPL", "order_side": "BUY", "order_venue": "SMART", "spec": { "Market": { "quantity": "1" } } } } }' ``` User bearer token example: ```bash theme={null} curl "https://api.anthid.com/v1/accounts?enabled=true" \ -H "Authorization: Bearer " ``` Service-account reads that cross organization boundaries may also require: ```text theme={null} x-organization-id: ``` For service reads that require `x-organization-id`, a missing or malformed header returns `400`. A caller context that requires an organization but has none returns `401`; policy denials remain `403`. Check the endpoint's response definitions for its exact access rules. ### gRPC The Streaming API accepts an organization API key or user bearer token in gRPC metadata. This example uses an API key. Clone the public protobuf repository so `grpcurl` can load the current split service definition. ```bash theme={null} git clone https://github.com/anthid-labs/proto.git anthid-proto grpcurl \ -import-path ./anthid-proto \ -proto trading/trading_service.proto \ -H "x-api-key: " \ -d '{ "trading_account_id": "YOUR_TRADING_ACCOUNT_ID", "environment": "BROKER_ENVIRONMENT_PAPER", "subscriptions": ["EVENT_TYPE_BROKER_ORDER"] }' \ stream.anthid.com:443 \ trading.TradingService/SubscribeEvents ``` ## Wire conventions Use each endpoint's schema for casing, required fields, and pagination. These conventions differ across APIs: | Value | Wire format | | - | - | | Quantities and prices | Decimal strings, such as `"10"` and `"195.00"` | | Page limits, counts, and sequence numbers | JSON integers where the schema declares integers | | Ledger environment | `paper` or `live`, required on set queries | | Order state / order status | `Open` / `PARTIALLY_FILLED`, with different casing | | Catalog asset class / stream instrument class | `EQUITY` / `equity` | | Order specification | Single-key object, such as `{"Market":{"quantity":"1"}}` | | Catalog instrument | Object tagged by `asset_class`, with class-specific fields | The [Instruments API](/api-reference/instruments/overview) supplies shared reference data. A listed instrument is not a promise of broker trading support. An `Idempotency-Key` on intent submission makes a retry safe: the same key with the same request is answered with the intent the first one made, and the same key with a different request is refused with `409`. See [Request correlation and retries](/api-reference/intents/overview#request-correlation-and-retries) before implementing automatic retries. ## Support For questions about authentication, request formats, or API behavior, contact [support](mailto:support@anthid.com). # Count completed intents Source: https://docs.anthid.com/api-reference/ledger/count-completed-intents /api-reference/ledger.openapi.json get /v1/ledger/intents/stats Counts distinct completed intents in the trailing window for the caller's organization. Amendments do not increase the count; live intents are excluded. # Get completed intents Source: https://docs.anthid.com/api-reference/ledger/get-completed-intents /api-reference/ledger.openapi.json get /v1/ledger/intents/history Lists filled, cancelled and rejected intents for the caller's organization. Returns one row per action; `terminal_only=true` selects the terminal action of each intent. Actions include `symbol`, resolved `instrument`, `terminal_status` and `terminal_at`. The plan history window bounds `start`, including when omitted, and is reported in `anthid-retention-days`. Live intents are available at `/v1/intents`. # Get current orders Source: https://docs.anthid.com/api-reference/ledger/get-current-orders /api-reference/ledger.openapi.json get /v1/orders Lists the latest state of broker orders for the caller's organization. Results can be filtered by account, symbol, intent, broker order ID, status, and state. Orders carry an `instrument` object tagged by `asset_class`; the symbol is available as `instrument.symbol`. # Get current positions Source: https://docs.anthid.com/api-reference/ledger/get-current-positions /api-reference/ledger.openapi.json get /v1/positions Lists the latest broker positions, filtered by account and symbol. Each includes the broker symbol and an `instrument` tagged by `asset_class`. An exact account-and-symbol lookup returns 404 when no position exists. Service callers need `intent:read` and `x-organization-id`. # Get exposure over time Source: https://docs.anthid.com/api-reference/ledger/get-exposure-over-time /api-reference/ledger.openapi.json get /v1/positions/exposure Reports positions at each bucket's close, hourly for one day and otherwise daily in New York. Positions carry forward until the broker reports a change. Notional uses average cost and the instrument multiplier. Each bucket includes long and short totals and an instrument breakdown, without netting across accounts. Requires a paid plan. # Get historical orders Source: https://docs.anthid.com/api-reference/ledger/get-historical-orders /api-reference/ledger.openapi.json get /v1/orders/history Lists historical broker order snapshots for the caller's organization, defaulting to the last 30 days within the plan history window. Explicit bounds expand to whole minutes and must span at most 92 days. Orders include an `instrument` tagged by `asset_class`; its symbol is `instrument.symbol`. # Get historical positions Source: https://docs.anthid.com/api-reference/ledger/get-historical-positions /api-reference/ledger.openapi.json get /v1/positions/history Lists historical broker position snapshots for the caller's organization, defaulting to the last 90 days within the plan history window. Explicit bounds expand to whole minutes and must span at most 92 days. Each position includes the broker symbol and an `instrument` tagged by `asset_class`. # Get order Source: https://docs.anthid.com/api-reference/ledger/get-order /api-reference/ledger.openapi.json get /v1/orders/{order_id} Returns the latest broker state for one order in the caller's organization. Read access is checked against the order account's environment. Orders include an `instrument`; its symbol is `instrument.symbol`. # Get order health signals Source: https://docs.anthid.com/api-reference/ledger/get-order-health-signals /api-reference/ledger.openapi.json get /v1/orders/health Reports amendment counts, including cancels, and stale orders in PENDING_NEW, PENDING_CANCEL, PENDING_REPLACE or UNSPECIFIED status. Amendment buckets end at 3 or more. Returns at most 100 stuck orders, oldest first; `stuck_count` includes all matches. Requires a paid plan. # Get order lifecycle metrics Source: https://docs.anthid.com/api-reference/ledger/get-order-lifecycle-metrics /api-reference/ledger.openapi.json get /v1/orders/lifecycle Reports order outcomes and latency in hourly buckets for one day, otherwise daily New York buckets. Orders count in their first-snapshot bucket; ingest lag is measured per snapshot. Percentiles are null when no samples exist. Requires a paid plan. # Get order stats Source: https://docs.anthid.com/api-reference/ledger/get-order-stats /api-reference/ledger.openapi.json get /v1/orders/stats Returns order counts and commission totals in hourly buckets for one day, otherwise daily New York buckets. `previous` covers the preceding window of equal length. Nonzero `unpriced_fill_count` means some filled orders lack a configured commission rate, so `fees` is incomplete. # Get platform handoff latency Source: https://docs.anthid.com/api-reference/ledger/get-platform-handoff-latency /api-reference/ledger.openapi.json get /v1/platform/latency Returns platform-wide intent-to-broker handoff latency in milliseconds. Uses the trailing hour when it has at least 20 samples; otherwise reads up to the latest 20 handoffs within seven days. `window_seconds` reports the measured span. `p50_ms` needs one sample; `p95_ms`, `p99_ms` and `max_ms` need 20 and are null below that threshold. # Get realized return per trading day Source: https://docs.anthid.com/api-reference/ledger/get-realized-return-per-trading-day /api-reference/ledger.openapi.json get /v1/orders/daily-returns Returns realized dollar profit or loss less recorded commission, grouped into Sunday-to-Saturday weeks in America/New_York. Each week includes all seven days. Opening fills count as trades and incur fees even when they realize no profit. Exchange and regulatory fees are excluded; fills without a configured commission rate add no fees. Accounts keep separate cost bases before their results are summed. Returns at most 12 weeks per page. Service callers need `intent:read` and `x-organization-id`. # List traded symbols Source: https://docs.anthid.com/api-reference/ledger/list-traded-symbols /api-reference/ledger.openapi.json get /v1/ledger/symbols/traded Returns sorted symbols traded in the trailing window or still held by the caller's organization. The window is capped by the caller's plan. Results are not paginated. # Ledger API Source: https://docs.anthid.com/api-reference/ledger/overview Query current and historical broker orders, positions, and order statistics. ## Availability Ledger APIs are available on all plans. Historical retention, query limits, and rate limits may vary by pricing tier. ## Intended Purpose The Anthid Ledger provides a durable historical record of trading activity across connected broker accounts. Rather than querying broker APIs directly for historical orders and positions, applications can use the Ledger APIs to access normalized, broker-agnostic records stored by Anthid. The Ledger is designed for historical analysis, reporting, auditing, monitoring, and application state reconstruction. It is also where completed intents are archived. Once an intent fills, is cancelled or is rejected, its full instruction chain is copied here asynchronously. Cleanup in the Intents API waits for archive confirmation, so completed intents may appear on both surfaces during the handoff. Archived instruction records are retained for six years; the plan controls the API query window. Current account state is also available through dedicated latest-state endpoints and real-time streaming APIs. ## Access Ledger endpoints read data for the authenticated user or API-key principal's organization. API-key callers need intent read permission for the `environment` requested (`paper:intent:read` or `live:intent:read`); user callers need an organization admin, member, or read-only role. When a request supplies an account filter, the trading account must belong to the caller's organization. Account filters are named `account_id` on every Ledger endpoint. ## `environment` is required Every Ledger endpoint that reads a set of records requires an `environment` query parameter. It is not optional and it has no default. Omitting it returns `400`. ```bash theme={null} curl "https://api.anthid.com/v1/orders?environment=paper" \ -H "x-api-key: " ``` | Value | Reads | | - | - | | `paper` | The organization's paper accounts | | `live` | The organization's live accounts | The parameter resolves to the organization's accounts in that environment, including accounts that are disabled and accounts that have been deleted. A deleted account keeps its records, so an order placed on an account the organization has since removed still appears in the environment it was placed in. Paper and live are never mixed in one response. A dashboard showing both has to make two requests. > [!NOTE] > > Two endpoints do not take `environment`, because neither reads a set: `GET /v1/orders/{order_id}` resolves one order by id, and `GET /v1/platform/latency` reports the platform rather than your organization. ## Endpoint Groups | Group | Endpoints | `environment` | | - | - | - | | Current positions | `GET /v1/positions` | Required | | Historical positions | `GET /v1/positions/history` | Required | | Position exposure | `GET /v1/positions/exposure` | Required | | Current orders | `GET /v1/orders` | Required | | Historical orders | `GET /v1/orders/history` | Required | | Single order | `GET /v1/orders/{order_id}` | Not accepted | | Order stats | `GET /v1/orders/stats` | Required | | Order lifecycle | `GET /v1/orders/lifecycle` | Required | | Order health | `GET /v1/orders/health` | Required | | Daily returns | `GET /v1/orders/daily-returns` | Required | | Historical intents | `GET /v1/ledger/intents/history` | Required | | Intent stats | `GET /v1/ledger/intents/stats` | Required | | Traded symbols | `GET /v1/ledger/symbols/traded` | Required | | Platform latency | `GET /v1/platform/latency` | Not accepted | Current and historical list endpoints support pagination with `next_cursor` and `limit`, plus sorting with `sort_by` and `sort_direction`. `limit` defaults to 100 and is capped at 1000. Send a response's non-null `next_cursor` value unchanged on the next request. ## Filters Order endpoints can filter by `account_id`, symbol, intent id, and external broker order id. Historical order endpoints also support `start` and `end` time ranges. Position endpoints can filter by `account_id` and symbol. Historical position endpoints also support `start` and `end` time ranges. Order stats require a trailing `days` value, clamped to 1 through 90, and can optionally filter by `account_id`. Historical intent endpoints can filter by `account_id`, symbol, intent id and terminal status, over a `start` and `end` range. `terminal_only=true` returns one row per intent instead of one per action. Intent stats take the same trailing `days` value as order stats, so the two can be reported side by side over one window. Intent stats count distinct intents rather than actions, so an order amended twice counts once. Symbols are normalized to uppercase, and a blank symbol is treated as no filter rather than as an empty symbol. Where a request reaches back further than the caller's plan allows, `start` is raised to the window the plan grants rather than refused. ## Analytics endpoints Beyond the record itself, the Ledger serves aggregates built over it. All of them require `environment`. | Endpoint | Answers | Window | | - | - | - | | `GET /v1/orders/stats` | How many orders, and how they ended, bucketed over the window | `days`, required, 1 to 90 | | `GET /v1/orders/lifecycle` | How long orders took and how they ended, with percentiles per bucket | `days`, required, 1 to 90 | | `GET /v1/orders/health` | The distribution of amendments per order, and orders stuck in a transitional status | `days`, required, 1 to 90 | | `GET /v1/positions/exposure` | What was held over the window, valued at broker average cost | `days`, required, 1 to 90 | | `GET /v1/orders/daily-returns` | Realized return and trade count per market day, grouped into whole weeks | `start` and `end` | | `GET /v1/ledger/symbols/traded` | The symbols traded or still held, for client-side prefix search | `days`, optional, defaults to 1 | A `days` window of 1 is reported as 24 hourly buckets; anything above that is reported daily. Every window is clamped again to the span the caller's plan grants. A few behaviors are worth knowing before plotting any of these. Percentiles are `null` for a bucket with no samples rather than zero, because a bucket in which nothing filled has no time to fill. Exposure reports levels, not flows. A position is carried across buckets the broker did not restate it in, so a quiet stretch repeats the preceding level instead of reading as flat. Daily returns are realized only. A fill that reduces or closes a position realizes against the average cost of what opened it. Opening a position adds to trade activity without realizing a return. See [Daily-return reconciliation](#daily-return-reconciliation) for broker adjustments and limitations. Order health limits stuck orders to transitional statuses on purpose. A resting good-till-cancelled limit is open for days by design and is not stuck. `stuck_minutes` defaults to 15. ## Daily-return reconciliation `GET /v1/orders/daily-returns` reports nominal dollar amounts, not percentages. `net_return` excludes fees, commissions, and unrealized gains. Books are tracked per account and symbol, so one account's sale cannot close another account's holding. The calculation starts from broker-reported opening holdings and replays recorded fills. It also compares subsequent broker positions with those fills to detect lasting differences caused by manual broker trades or missing order updates. Brief reporting lag is allowed to settle before a correction is applied. When broker cash totals supply enough information, the correction includes a reconstructed trade price and realized return. Without a price, Anthid corrects inventory and cost basis but cannot assign a realized return to the missing trade. Some round trips against existing inventory also lack enough information to value. This makes the result dependent on broker reporting; it is not a complete broker statement or a tax report. `trade_count` includes reconstructed activity as well as recorded filled orders. A recovered round trip contributes two trades. It is not a count of intents, per-fill events, or only profitable closes. Broker P\&L and cash totals used internally for reconciliation are not public position response fields. Results contain whole Sunday-through-Saturday weeks, oldest first, including zero-activity days. Dates use New York market time. Each week's `net_return` and `trade_count` sum its seven days. Unlike opaque list cursors, this endpoint's `next_cursor` is the next Sunday as `YYYY-MM-DD`; pass it back unchanged with the requested range and environment. ## Order stats series Order stats return counts for the whole window at the top level, a `data` array breaking the same counts down over time, oldest bucket first, and a `previous` object holding the same totals for the window immediately before this one. | `days` | Bucket width | Buckets returned | | - | - | - | | `1` | Hour | 24 | | `2` and above | Day | One per day | Every bucket in the window is present even when no orders landed in it, so the series can be plotted without filling gaps. The window is aligned to bucket boundaries, which makes the newest bucket the hour or day currently in progress. Each `timestamp` is the RFC 3339 start of its bucket. Daily buckets start at midnight in New York and timestamps are returned in UTC, so their UTC offset changes with daylight saving time. Hourly buckets start on the hour. The top-level counts are the sum of the buckets. Every count is of orders, not of the snapshots an order produced on its way to closing. An order is counted in the bucket it was placed in, and by the state it ended in. An order that closed stays closed: a command the broker refused after the order had already finished, such as a reprice that raced its own fill, is a refused command and not a rejected order, so it moves neither `closed_count` nor `rejected_count`. `rejected_count` is the orders the broker never accepted. ```json theme={null} { "order_count": 12, "closed_count": 9, "rejected_count": 1, "filled_count": 8, "cancelled_count": 2, "previous": { "order_count": 9, "closed_count": 7, "rejected_count": 2, "filled_count": 5, "cancelled_count": 2 }, "data": [ { "timestamp": "2026-07-30T10:00:00Z", "order_count": 5, "closed_count": 4, "rejected_count": 0, "filled_count": 4, "cancelled_count": 0 }, { "timestamp": "2026-07-30T11:00:00Z", "order_count": 0, "closed_count": 0, "rejected_count": 0, "filled_count": 0, "cancelled_count": 0 } ] } ``` `previous` covers the window of equal length immediately before the reported one: the 24 hours before the reported 24, the 7 days before the reported 7. It is the baseline a period-over-period change is measured against. > [!NOTE] > > Use `previous` rather than deriving a comparison from `data`. Splitting `data` in half only ever compares the window against itself, which reports a quiet second half as a fall even when the window matches the one before it. ```bash theme={null} curl "https://api.anthid.com/v1/orders/history?environment=live&account_id=&symbol=AAPL&limit=50&sort_by=received_at&sort_direction=Desc" \ -H "x-api-key: " ``` ## Order identifiers Every order carries a platform identifier and, once the broker acknowledges it, the broker's own. | Identifier | Minted by | Available | | - | - | - | | `order_id` | Anthid | As soon as Anthid has an order record | | `external_order_id` | The broker | Only after the broker acknowledges the order | | `intent_id` | Anthid, or you if you named it on the create | From submission, on the intent that owns the order | `external_order_id` is nullable. An order Anthid sent but the broker has not yet acknowledged has a platform identifier and no broker one, and an order the broker rejected outright may never receive a broker identifier at all. Use `order_id` within Anthid and `external_order_id` when reconciling against anything outside it. Both are queryable filters. ## Order state and status Each order carries `order_status`, the detailed lifecycle position in the venue's vocabulary, and `order_state`, a coarse rollup derived from it. `order_state` is computed from `order_status` rather than reported separately, so the two cannot disagree. > [!WARNING] > > **The two fields do not share a casing.** `order_state` serializes as `Open`, `Closed`, `Rejected`, `Unspecified`. `order_status` beside it serializes as `NEW`, `FILLED`, `PARTIALLY_FILLED`. Comparing an `order_state` against `"OPEN"` never matches. | `order_state` | Statuses that map to it | | - | - | | `Open` | `PENDING_NEW`, `NEW`, `PARTIALLY_FILLED`, `PENDING_CANCEL`, `PENDING_REPLACE`, `REPLACED` | | `Closed` | `FILLED`, `CANCELLED`, `STOPPED` | | `Rejected` | `REJECTED` | | `Unspecified` | `UNSPECIFIED` | > [!WARNING] > > **`PARTIALLY_FILLED` is `Open`, and `Closed` is not a synonym for filled.** An order with fills against it is still working; closing it out in your own model on the first fill strands the remainder. A cancelled order and a fully filled order are both `Closed`. > > `filled_quantity` is the only field that reports how much traded, and it is meaningful at every state: partial on an open order, complete on a filled one, and zero on a cancelled one that never traded. Do not infer execution from `order_state`. ## Order fields | Field | What it holds | | - | - | | `symbol`, `order_side`, `order_type` | What was being traded, and how | | `quantity`, `filled_quantity` | Ordered size and the amount executed so far | | `limit_price`, `stop_price` | The order's price terms, where its type uses them | | `average_price` | Average execution price across fills | | `time_in_force`, `order_venue` | How long it was to remain working, and where it was sent | | `broker`, `account_id` | Which broker and which trading account it belongs to | | `received_at` | When Anthid observed this version of the order | ## Position fields | Field | What it holds | | - | - | | `symbol` | The instrument held | | `quantity` | Size of the holding | | `average_cost` | Average cost basis across the fills that built it | | `account_id`, `broker` | Which trading account and broker the holding sits with | | `received_at` | When Anthid observed this version of the position | Position records are relayed from the broker rather than derived from observed fills, so `received_at` is the age of the information rather than the age of the holding. See [Positions](/pages/concepts/positions) for why that choice was made. ## Reconciling after a stream disconnect Reconnecting does not replay every missed transition. Positions are primed with their latest retained state; orders can request the same behavior using `ORDER_DELIVERY_PRIMED`. Wait for the corresponding `primed` marker before treating the initial delivery as complete. For reconciliation beyond retained stream state, read the current endpoints: `GET /v1/orders` and `GET /v1/positions` return the latest known state per order and per symbol, which is what closes the gap. Both need `environment`, and a client watching both paper and live has to reconcile each separately. ```bash theme={null} curl "https://api.anthid.com/v1/orders?environment=live" \ -H "x-api-key: " ``` See the [Stream overview](/api-reference/stream/overview) for the reconnection pattern. Stream retention is shorter than ledger retention: terminal orders and flat positions expire from stream state after one hour. Start a fresh primed snapshot set rather than merging it into an old cache. While paging REST results, keep receiving live events and compare `received_at` before replacing newer state. Current orders include terminal orders; filter `order_state` for a working-order view. ## Common Use Cases * Retrieve historical order activity. * Retrieve historical position snapshots. * Retrieve latest known orders and positions. * Query aggregate order counts over a trailing day window, hour by hour or day by day. * Build trading dashboards and reporting systems. * Audit account activity across broker integrations. * Reconstruct application state from historical records. * Analyze execution activity over time. * Export trading data for compliance or operational review. * Power internal analytics and monitoring workflows. ## Data Model The Ledger stores broker activity as normalized platform records. Records are organized around: * Orders * Positions Data is indexed and queryable by organization, environment, trading account, symbol, time range, intent id, and external broker order id where supported. Historical records are immutable and intended to represent the broker activity observed by Anthid at the time it occurred. ## Historical vs Latest Data Anthid provides two categories of data access: ### Historical Historical endpoints return records across a specified time range and are intended for reporting, auditing, analytics, and data export workflows. ### Latest Latest endpoints return the most recent known state for orders and positions and are intended for dashboards, account views, and operational workflows. Applications should generally use latest endpoints for current state and historical endpoints for analysis and reporting. ## Related Resources * [Orders](/pages/concepts/orders) * [Positions](/pages/concepts/positions) * [Streaming API](/api-reference/stream/overview) * [Trading Accounts](/api-reference/accounts/overview) * [Brokers](/pages/brokers/overview) ## Support For questions about authentication, request formats, or API behavior, contact [support@anthid.com](mailto:support@anthid.com). # Delete organization control Source: https://docs.anthid.com/api-reference/organization-controls/delete-organization-control /api-reference/controls.openapi.json delete /v1/controls/organizations Deletes the caller's organization manual control. Requires Controls Premium. Users need an admin role; API keys need both `paper:control:edit` and `live:control:edit`. # Get current organization control Source: https://docs.anthid.com/api-reference/organization-controls/get-current-organization-control /api-reference/controls.openapi.json get /v1/controls/organizations Returns the caller's organization manual control. Requires Controls Premium. Users need an organization role; API keys need both `paper:control:read` and `live:control:read`. # Get organization control Source: https://docs.anthid.com/api-reference/organization-controls/get-organization-control /api-reference/controls.openapi.json get /v1/controls/organizations/{org_id} Returns the manual control for an organization. This route requires service-account credentials with `control:read`. # Patch organization control Source: https://docs.anthid.com/api-reference/organization-controls/patch-organization-control /api-reference/controls.openapi.json patch /v1/controls/organizations Creates or patches the caller's organization manual control. Omitted and null fields are unchanged; `clear` removes named fields. Requires Controls Premium. Users need an admin role; API keys need both `paper:control:edit` and `live:control:edit`. # Upsert organization control Source: https://docs.anthid.com/api-reference/organization-controls/upsert-organization-control /api-reference/controls.openapi.json put /v1/controls/organizations Creates or replaces the caller's organization manual control. Omitted optional fields are cleared. Requires Controls Premium. Users need an admin role; API keys need both `paper:control:edit` and `live:control:edit`. # Create an organization Source: https://docs.anthid.com/api-reference/organizations/create-an-organization /api-reference/organizations.openapi.json post /v1/organizations Starts organization setup for the authenticated user and returns its ID before provider setup completes. The required `idempotency_key` identifies the workflow; repeating it returns the original organization ID. Only user principals may call this. # Delete current user Source: https://docs.anthid.com/api-reference/organizations/delete-current-user /api-reference/organizations.openapi.json delete /v1/users/me Permanently deletes the authenticated user at the identity provider and queues a deletion event. Requires a user principal with an active organization. # Delete organization Source: https://docs.anthid.com/api-reference/organizations/delete-organization /api-reference/organizations.openapi.json delete /v1/organizations/{org_id} Deletes an organization and queues removal of its external identity record. The caller must be an organization admin and retain membership in another organization. # Get current user Source: https://docs.anthid.com/api-reference/organizations/get-current-user /api-reference/organizations.openapi.json get /v1/users/me Returns the authenticated user's name and email address from the identity provider. # Get organization Source: https://docs.anthid.com/api-reference/organizations/get-organization /api-reference/organizations.openapi.json get /v1/organizations/{org_id} Returns an organization by its internal ID. Users may read any organization they belong to; API keys may read only their own organization. Service tokens require `organization:read`. # Get organization by provider id Source: https://docs.anthid.com/api-reference/organizations/get-organization-by-provider-id /api-reference/organizations.openapi.json get /v1/organizations/by-provider/{provider}/{provider_id} Returns an organization by its identity-provider ID. Service accounts can read any organization; users can read organizations they belong to. # Get organization onboarding Source: https://docs.anthid.com/api-reference/organizations/get-organization-onboarding /api-reference/organizations.openapi.json get /v1/organizations/{org_id}/onboarding Returns onboarding completion timestamps. Users may read any organization they belong to; API keys may read only their own organization. Service tokens require `organization:read`. # Get user by id Source: https://docs.anthid.com/api-reference/organizations/get-user-by-id /api-reference/organizations.openapi.json get /v1/users/{user_id} Returns a named user's name and email address from the identity provider. Authorized service accounts can resolve any user; a signed-in user can resolve only themselves. # List current user organizations Source: https://docs.anthid.com/api-reference/organizations/list-current-user-organizations /api-reference/organizations.openapi.json get /v1/organizations Lists the organizations the authenticated user belongs to. # Organizations API Source: https://docs.anthid.com/api-reference/organizations/overview Manage users, organizations, and organization onboarding state. The Organizations API manages the identity and organization context used by the rest of the Anthid platform. ## Access User profile, organization creation, organization updates, and organization deletion require a user bearer token: ```text theme={null} Authorization: Bearer ``` Selected organization reads also accept organization API keys or scoped service tokens. Onboarding updates accept users who belong to the organization or services with `organization:read`; API keys cannot update onboarding. Check each endpoint's authentication section before choosing a credential. ## Create an organization Organization creation is a reconciled workflow. The API returns the new internal organization ID after it records the request; the backing identity-provider organization and related records are created asynchronously. Every request requires a caller-generated UUID in `idempotency_key`. Reuse the same key when retrying the same logical creation request, and generate a new key for a different organization. ```bash theme={null} curl https://api.anthid.com/v1/organizations \ -X POST \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "name": "Acme Trading", "idempotency_key": "550e8400-e29b-41d4-a716-446655440000" }' ``` ```json theme={null} { "organization_id": "019c0538-86f2-7c32-9c1e-6f79cbfdb7b4" } ``` ## Onboarding The onboarding resource records when an organization completes these milestones: * `ConnectedBroker` * `CreatedApiKey` * `SubmittedFirstOrder` * `ViewedDocs` * `ConfiguredControls` Mark a milestone complete with `PATCH /v1/organizations/{org_id}/onboarding`. The operation returns `204 No Content`. ```json theme={null} { "step": "ConnectedBroker" } ``` ## Deletes `DELETE /v1/organizations/{org_id}` returns a body rather than an empty response: ```json theme={null} { "deleted": true, "deleted_at": "2026-08-12T16:20:00Z", "id": "550e8400-e29b-41d4-a716-446655440222", "resource": "organization" } ``` An authorized retry returns `200` with the original `deleted_at`. Authorization is checked again, so a retry can fail if the caller no longer has access to the organization. `resource` is drawn from one vocabulary shared across the platform rather than written as free text, so a caller handling deletes from several APIs branches on the field instead of on which URL it called. The vocabulary names resources this API does not delete itself, including `trading_account` and `test_trading_account` from the [Trading Accounts API](/api-reference/accounts/overview). `DELETE /v1/users/me` is the exception and returns `204 No Content`. ## Endpoint groups | Group | Endpoints | | - | - | | Current user | `GET`, `PUT`, and `DELETE /v1/users/me` | | Users | `GET /v1/users/{user_id}` | | Organizations | `GET` and `POST /v1/organizations`; `GET`, `PATCH`, and `DELETE /v1/organizations/{org_id}` | | Provider lookup | `GET /v1/organizations/by-provider/{provider}/{provider_id}` | | Onboarding | `GET` and `PATCH /v1/organizations/{org_id}/onboarding` | # Update current user Source: https://docs.anthid.com/api-reference/organizations/update-current-user /api-reference/organizations.openapi.json put /v1/users/me Updates the authenticated user's metadata and locale at the identity provider. Supplied metadata replaces the existing map. Requires a user principal with an active organization. # Update organization name Source: https://docs.anthid.com/api-reference/organizations/update-organization-name /api-reference/organizations.openapi.json patch /v1/organizations/{org_id} Updates the active organization's name. The caller must be an organization admin; authorization uses the active organization context. # Update organization onboarding Source: https://docs.anthid.com/api-reference/organizations/update-organization-onboarding /api-reference/organizations.openapi.json patch /v1/organizations/{org_id}/onboarding Marks one onboarding step complete. Users may update any organization they belong to. Service tokens require `organization:read`. API keys cannot update onboarding. # Node.js Source: https://docs.anthid.com/api-reference/stream/nodejs Connect to Anthid's Streaming API using Node.js and gRPC. Anthid provides Protocol Buffer definitions that can be used to generate Node.js clients for the Streaming API. This guide demonstrates connecting to the Streaming API and receiving real-time broker order and position updates. ## Prerequisites Install the required dependencies. ```bash theme={null} npm install @grpc/grpc-js @grpc/proto-loader ``` ## Download the Protobuf Definitions ```bash theme={null} git clone https://github.com/anthid-labs/proto.git anthid-proto ``` ## Load the Proto Definitions ```javascript theme={null} const grpc = require("@grpc/grpc-js"); const path = require("node:path"); const protoLoader = require("@grpc/proto-loader"); const protoRoot = path.resolve("anthid-proto"); const packageDefinition = protoLoader.loadSync( path.join( protoRoot, "trading", "trading_service.proto", ), { includeDirs: [protoRoot], keepCase: false, longs: String, enums: String, defaults: true, oneofs: true, }, ); const proto = grpc.loadPackageDefinition( packageDefinition, ); const trading = proto.trading; ``` ## Authentication Streaming requests authenticate with either a Bearer access token or the `x-api-key` metadata header. This guide uses an API key. ```javascript theme={null} const metadata = new grpc.Metadata(); metadata.add( "x-api-key", "YOUR_API_KEY", ); ``` ## Subscribe to Events The `SubscribeEvents` endpoint provides a server-streaming interface suitable for most applications. ```javascript theme={null} const grpc = require("@grpc/grpc-js"); const path = require("node:path"); const protoLoader = require("@grpc/proto-loader"); const protoRoot = path.resolve("anthid-proto"); const packageDefinition = protoLoader.loadSync( path.join( protoRoot, "trading", "trading_service.proto", ), { includeDirs: [protoRoot], keepCase: false, longs: String, enums: String, defaults: true, oneofs: true, }, ); const proto = grpc.loadPackageDefinition( packageDefinition, ); const client = new proto.trading.TradingService( "stream.anthid.com:443", grpc.credentials.createSsl(), ); const metadata = new grpc.Metadata(); metadata.add( "x-api-key", "YOUR_API_KEY", ); const stream = client.SubscribeEvents( { tradingAccountId: "YOUR_TRADING_ACCOUNT_ID", environment: "BROKER_ENVIRONMENT_PAPER", subscriptions: [ "EVENT_TYPE_BROKER_ORDER", "EVENT_TYPE_BROKER_POSITION", ], }, metadata, ); stream.on("data", (event) => { if (event.heartbeat) { console.log("heartbeat"); } else if (event.status) { console.log( "status:", event.status.message, ); } else if (event.error) { console.log( "error:", event.error.message, ); } else if (event.primed) { console.log("initial state complete:", event.primed.eventType); } else if (event.brokerOrder) { console.log( "order:", event.brokerOrder, ); } else if (event.brokerPosition) { console.log( "position:", event.brokerPosition, ); } }); stream.on("error", (error) => { console.error(error); }); stream.on("end", () => { console.log( "stream disconnected", ); }); ``` ## Bidirectional Streaming Backend and long-running services can use `StreamEvents` to dynamically subscribe and unsubscribe from accounts over an existing connection. ```javascript theme={null} const stream = client.StreamEvents( metadata, ); stream.write({ subscribe: { tradingAccountId: "YOUR_TRADING_ACCOUNT_ID", environment: "BROKER_ENVIRONMENT_PAPER", subscriptions: [ "EVENT_TYPE_BROKER_ORDER", "EVENT_TYPE_BROKER_POSITION", ], }, }); stream.on("data", (event) => { console.log(event); }); ``` With `keepCase: false`, request and response fields use camel case: `tradingAccountId`, `environment`, `orderDelivery`, and `event.primed.eventType`. `environment` is required on every subscribe and unsubscribe request: `"BROKER_ENVIRONMENT_PAPER"` or `"BROKER_ENVIRONMENT_LIVE"`, matching the account's environment. Set `orderDelivery: "ORDER_DELIVERY_PRIMED"` on the subscribe request to receive retained order snapshots first. Wait for the order `primed` event before treating that initial delivery as complete. Terminal orders and zero-quantity positions expire from retained stream state after one hour. Rebuild a fresh snapshot set on reconnect so an old cache does not retain entries that no longer replay. See [Rebuilding a cached view](/api-reference/stream/overview#rebuilding-a-cached-view). ## Unsubscribe To stop receiving events for an account, send an unsubscribe request on the same stream. ```javascript theme={null} stream.write({ unsubscribe: { tradingAccountId: "YOUR_TRADING_ACCOUNT_ID", environment: "BROKER_ENVIRONMENT_PAPER", }, }); ``` ## Handling Disconnects Applications should automatically reconnect when a stream closes unexpectedly. After reconnecting: 1. Create a new stream. 2. Authenticate using a Bearer access token or API key. 3. Re-subscribe to required trading accounts. 4. Resume event processing. ## TypeScript The same approach works in TypeScript. Many teams choose to generate strongly typed TypeScript definitions from the Anthid protobuf files during their build process rather than loading protobuf definitions dynamically at runtime. ## Related Resources * Streaming Overview * Python Guide * Rust Guide * Trading Accounts * Orders * Positions * Ledger * Intents # Streaming Source: https://docs.anthid.com/api-reference/stream/overview Receive real-time order and position updates through Anthid's gRPC streaming APIs. Anthid provides real-time broker updates through gRPC streaming. While REST APIs are used for creating intents and querying historical data, the Streaming API delivers live order and position updates as they occur. Streaming allows applications to react immediately to broker activity without polling REST endpoints. ## Availability Streaming is available on all plans. Organizations may maintain multiple concurrent stream connections subject to plan limits. API-key connection limits are resolved from the organization's billing limits. ## Access Streaming requests must be authenticated and associated with an organization. Client examples use the `x-api-key` gRPC metadata header. Subscriptions are scoped to trading accounts owned by the authenticated principal's organization. Every request must name a broker `environment`, paper or live, and include at least one event type. A request may name one valid trading account UUID in that environment, or omit `trading_account_id` to subscribe across the organization's accounts in that environment. API-key callers need `paper:intent:read` or `live:intent:read` for the environment subscribed to. ## Subscriptions Once connected, your client can subscribe to message types for a trading account. The subscription messages are defined in protobuf via the proto repository: [anthid-labs/proto](https://github.com/anthid-labs/proto/). ## Service Definition ```proto theme={null} service TradingService { rpc StreamEvents(stream StreamEventsRequest) returns (stream TradingEvent); rpc SubscribeEvents(Subscribe) returns (stream TradingEvent); } ``` ## Streaming Models Anthid supports two streaming patterns. ### StreamEvents A bidirectional gRPC stream intended for backend and native applications. Clients can dynamically subscribe and unsubscribe from trading accounts over an existing connection. ### SubscribeEvents A server-streaming endpoint intended for clients with a fixed subscription. The subscription is established when the request is created and remains active for the lifetime of the stream. ## Endpoints | RPC | Request | Response | Use case | | - | - | - | - | | `StreamEvents` | Client stream of `StreamEventsRequest` messages | Server stream of `TradingEvent` messages | Dynamic subscribe and unsubscribe over one connection | | `SubscribeEvents` | Single `Subscribe` message | Server stream of `TradingEvent` messages | One fixed subscription per stream | ## Authentication Streaming requests authenticate with either a Bearer access token or an API key in gRPC metadata. The examples use an API key. ```text theme={null} x-api-key: YOUR_API_KEY ``` ```text theme={null} authorization: Bearer YOUR_ACCESS_TOKEN ``` ## Event Types Applications can subscribe to one or more event types. | Event Type | Description | | - | - | | EVENT\_TYPE\_BROKER\_ORDER | Real-time order lifecycle updates | | EVENT\_TYPE\_BROKER\_POSITION | Real-time position updates | | EVENT\_TYPE\_BROKER\_TRADE | Individual executions against an order | | EVENT\_TYPE\_INTENT\_ACTION | A command Anthid has committed before the broker responds | ## Subscription Requests ### Subscribe ```json theme={null} { "trading_account_id": "ACCOUNT_ID", "environment": "BROKER_ENVIRONMENT_PAPER", "subscriptions": [ "EVENT_TYPE_BROKER_ORDER", "EVENT_TYPE_BROKER_POSITION" ] } ``` | Field | Description | | - | - | | `environment` | Required. `BROKER_ENVIRONMENT_PAPER` or `BROKER_ENVIRONMENT_LIVE`. An unspecified environment is refused with an error event. | | `trading_account_id` | A trading account UUID in that environment, or empty for the organization. An account in the other environment is refused. | | `subscriptions` | One or more event types. | | `order_delivery` | `ORDER_DELIVERY_LIVE` (default) or `ORDER_DELIVERY_PRIMED`. | | `instrument_key`, `instrument_class` | Optional instrument filter for a named account. | Leave `trading_account_id` out, or send it empty, to subscribe to every trading account your organization has in the requested environment. Each event names the account it belongs to, so one stream can carry the whole organization. The organization is taken from the credentials on the request, never from the request body, so this is still only ever your own accounts. The account set is fixed when the subscription is made: an account added afterwards is not included until you subscribe again. An organization with no accounts in the environment is refused. Organization-wide subscriptions are one per environment, so a client can hold paper and live side by side. ```json theme={null} { "environment": "BROKER_ENVIRONMENT_PAPER", "subscriptions": [ "EVENT_TYPE_BROKER_ORDER", "EVENT_TYPE_BROKER_POSITION" ] } ``` ## Initial state and live delivery | Event kind | On each subscription | | - | - | | Orders, default or `ORDER_DELIVERY_LIVE` | New updates after attachment | | Orders, `ORDER_DELIVERY_PRIMED` | Latest retained snapshot per order, then live updates | | Positions | Latest retained snapshot per instrument, then live updates | | Trades and intent actions | New events after attachment | Request primed orders when you need to rebuild a view of retained order state: ```json theme={null} { "trading_account_id": "ACCOUNT_ID", "environment": "BROKER_ENVIRONMENT_PAPER", "subscriptions": ["EVENT_TYPE_BROKER_ORDER", "EVENT_TYPE_BROKER_POSITION"], "order_delivery": "ORDER_DELIVERY_PRIMED" } ``` The server sends a `primed` event with `event_type` once each requested primed kind has finished its initial delivery, including when there are no retained messages. Positions always receive this marker; orders receive it only with primed delivery. A `status` confirmation acknowledges the subscription and does not mean initial state is complete. Working orders and nonzero positions remain in current stream state until updated. Terminal-order snapshots and zero-quantity position snapshots expire one hour after publication. Initial snapshots can therefore include recently completed orders and recently flat positions, but not a complete history. Expiry does not emit a public deletion event. Upsert them by order ID and filter by order state for a working-order view. A snapshot is not a new execution. Priming does not replay all historical transitions or provide a resume cursor. Use the ledger to reconcile history beyond retained stream state. ### Rebuilding a cached view Build a fresh snapshot set for each subscription scope. Upsert initial and subsequent messages by order ID or account and instrument, then use the `primed` marker to complete that kind's initial view. Merging primed messages into an old cache without removing stale entries can leave orders or positions that expired while disconnected. For a live-only order subscription, reconcile through REST instead. Keep processing live updates during REST reconciliation. Do not let an older REST snapshot overwrite a newer stream update; compare `received_at`. Page through all results and filter order state for a working-order view. The ledger retains terminal orders after their stream snapshots expire. ### Instrument filters For a named trading account, set `instrument_key` to narrow orders and positions to an instrument. `instrument_class` uses lowercase subject values such as `equity`, unlike REST catalog asset classes such as `EQUITY`. Leave `instrument_class` empty to match any class, or specify a class supported by the protobuf model. An instrument class alone does not select an instrument. Until instrument metadata is resolved consistently, specifying a class can exclude otherwise matching updates. Organization-wide subscriptions ignore these filters. Trades and intent actions remain account-scoped even when an instrument filter is set, so filter those payloads in your client if needed. ### Unsubscribe `Unsubscribe` is only available on the bidirectional `StreamEvents` RPC. ```json theme={null} { "trading_account_id": "ACCOUNT_ID", "environment": "BROKER_ENVIRONMENT_PAPER" } ``` `environment` is required, as on `Subscribe`. An empty `trading_account_id` unsubscribes the organization-wide subscription for the named environment, matching how it was subscribed; the other environment's subscription stays. It does not cancel the per-account subscriptions, which are separate and are each unsubscribed by name. ## Stream Events The server may emit the following event types. ### Heartbeat Indicates that the connection remains healthy. The server emits heartbeats approximately every 5 seconds. ### Status Returned when a subscription is added or removed successfully. Successful messages are `subscribed` and `unsubscribed`. ### Error Returned when a request cannot be processed. ### BrokerOrder Represents a real-time order lifecycle update. Events may include: * New orders * Accepted orders * Partial fills * Filled orders * Cancelled orders * Rejected orders ### BrokerPosition Represents a real-time position update received from a broker. Position updates are emitted whenever Anthid receives a change in account holdings. ### BrokerTrade Represents one execution against an order. An order can fill in many pieces, so this is the fill-by-fill record that `BrokerOrder` aggregates into `filled_quantity` and `average_price`. | Field | Holds | | - | - | | `account_id`, `organization_id` | Which trading account and organization the execution belongs to | | `order_id` | The broker's own order id | | `client_order_id` | The platform order id the execution belongs to | | `intent_id` | Optional intent the execution belongs to, the same id `BrokerOrder` carries. Absent for executions on orders Anthid did not place, such as a manual trade at the broker | | `symbol`, `side` | What traded, and in which direction | | `quantity` | Instrument units in **this** execution, not the order's cumulative quantity | | `price` | Price of **this** execution, not the order's average price | | `received_at` | When Anthid observed the execution | | `exec_id` | Optional broker execution ID; identifies the fill rather than the order | | `cumulative_quantity`, `order_quantity`, `leaves_quantity` | Optional order totals as of this execution | | `broker_sent_at` | Optional timestamp from the broker's clock, for reporting | | `instrument` | Optional resolved instrument metadata | | `command_seq` | Optional sequence of the last command applied to the order when this fill executed | Optional fields depend on the broker and producer. Use the execution's own totals and `command_seq` when present: an independently received order update may describe a replacement that raced the fill. An accepted replacement is not necessarily applied yet. Absent `command_seq` means the applied command is unknown, not zero. Use `received_at` for local ordering. `broker_sent_at` is the broker's reported time and can disagree with other reports of the same event. Keep decimal quantities and prices as strings or exact decimals. Scope execution IDs by account and broker order when deduplicating; an order ID alone cannot distinguish multiple fills. > [!NOTE] > > Subscribe to this only if you need per-fill detail, such as building a VWAP or reconciling against a broker's execution report. If you want to know how much of an order has traded, read `filled_quantity` on `BrokerOrder`, which already aggregates these. ### IntentAction Represents a create, replace, or cancel command Anthid has durably accepted. It is the platform's account of what it was asked to do, before and independently of any broker response. Internal broker heartbeat events are not forwarded on the public stream. ## Connection Lifecycle 1. Connect to the streaming endpoint. 2. Authenticate using a Bearer access token or API key. 3. Subscribe to one or more trading accounts. 4. Receive status confirmation. 5. Receive real-time events. 6. Reconnect and resubscribe if the connection is interrupted. Streams are connection-oriented. If a stream closes, create a new stream and resubscribe to the required trading accounts. > [!WARNING] > > **Reconnecting does not replay every missed event.** Orders default to live delivery. Request `ORDER_DELIVERY_PRIMED` to receive their latest retained snapshots before following live updates. Trades and intent actions are live-only. > > **Positions are always primed.** On subscribing you receive the latest retained position for each instrument in scope, then a `primed` marker and live updates. Treat the initial messages as state rather than as changes that just happened. > > After any disconnect, reconcile against the ledger's current endpoints rather than assuming continuity. `GET /v1/orders?environment=live` and `GET /v1/positions?environment=live` return the latest known state and are what close the gap. `environment` is required on both. See [Reconciling after a stream disconnect](/api-reference/ledger/overview#reconciling-after-a-stream-disconnect). ## Recommended Usage Use the Streaming API for: * Live order monitoring * Position synchronization * Trading dashboards * Notifications * Automated trading workflows Use REST APIs for: * Intent submission * Historical queries * Search and filtering * Reporting * Reconciliation workflows ## Related Resources * [Node.js Guide](/api-reference/stream/nodejs) * [Python Guide](/api-reference/stream/python) * [Rust Guide](/api-reference/stream/rust) * [Intents](/pages/concepts/intents) * [Orders](/pages/concepts/orders) * [Positions](/pages/concepts/positions) * [Trading Accounts](/api-reference/accounts/overview) * [Ledger](/api-reference/ledger/overview) ## Support For questions about authentication, request formats, or API behavior, contact [support@anthid.com](mailto:support@anthid.com). # Python Source: https://docs.anthid.com/api-reference/stream/python Connect to Anthid's Streaming API using Python. Anthid provides generated Protocol Buffer definitions that can be used to build Python clients for the Streaming API. This guide demonstrates how to generate the Python gRPC client and subscribe to real-time trading events. ## Prerequisites Install the required Python packages. ```bash theme={null} python -m pip install grpcio grpcio-tools protobuf ``` ## Download the Protobuf Definitions Clone the Anthid protobuf repository. ```bash theme={null} git clone https://github.com/anthid-labs/proto.git anthid-proto ``` ## Generate Python Client Code Generate Python classes from the protobuf definitions. ```bash theme={null} mkdir -p generated/python python -m grpc_tools.protoc \ -I anthid-proto \ --python_out=generated/python \ --grpc_python_out=generated/python \ anthid-proto/trading/*.proto export PYTHONPATH="$PWD/generated/python:$PYTHONPATH" ``` The generated `trading` namespace contains the message definitions and gRPC service stubs required to interact with Anthid services. ## Authentication Streaming requests authenticate with either a Bearer access token or an API key passed as gRPC metadata. This guide uses an API key. ```python theme={null} metadata = ( ("x-api-key", "YOUR_API_KEY"), ) ``` ## Subscribe to Events The example below demonstrates connecting to the server-streaming endpoint and subscribing to broker order and position events. ```python theme={null} import grpc from trading import trading_service_pb2 as trading_pb2 from trading import trading_service_pb2_grpc as trading_pb2_grpc from trading import trading_types_pb2 API_KEY = "YOUR_API_KEY" request = trading_pb2.Subscribe( trading_account_id="YOUR_TRADING_ACCOUNT_ID", environment=trading_types_pb2.BROKER_ENVIRONMENT_PAPER, subscriptions=[ trading_pb2.EVENT_TYPE_BROKER_ORDER, trading_pb2.EVENT_TYPE_BROKER_POSITION, ], ) credentials = grpc.ssl_channel_credentials() with grpc.secure_channel( "stream.anthid.com:443", credentials, ) as channel: stub = trading_pb2_grpc.TradingServiceStub( channel, ) stream = stub.SubscribeEvents( request, metadata=( ("x-api-key", API_KEY), ), ) for event in stream: if event.HasField("heartbeat"): print("heartbeat") elif event.HasField("status"): print( "status:", event.status.message, ) elif event.HasField("error"): print( "error:", event.error.message, ) elif event.HasField("primed"): print("initial state complete:", event.primed.event_type) elif event.HasField("broker_order"): print( "order:", event.broker_order, ) elif event.HasField("broker_position"): print( "position:", event.broker_position, ) ``` `environment` is required: set it to `trading_types_pb2.BROKER_ENVIRONMENT_PAPER` or `trading_types_pb2.BROKER_ENVIRONMENT_LIVE` to match the account. The enum is defined in `trading_types.proto`, so it is generated into `trading_types_pb2` rather than `trading_service_pb2`. To receive retained order snapshots first, set `order_delivery=trading_pb2.ORDER_DELIVERY_PRIMED` in `Subscribe`. Positions always prime retained state. Wait for the corresponding `primed` event before treating initial delivery as complete. Terminal orders and zero-quantity positions expire from retained stream state after one hour. Rebuild a fresh snapshot set on reconnect so an old cache does not retain entries that no longer replay. See [Rebuilding a cached view](/api-reference/stream/overview#rebuilding-a-cached-view). ## Bidirectional Streaming Backend and native applications may prefer the bidirectional `StreamEvents` endpoint. This endpoint allows subscriptions and unsubscriptions to be sent over an existing stream without creating a new connection. ```python theme={null} import grpc from queue import Queue from trading import trading_service_pb2 as trading_pb2 from trading import trading_service_pb2_grpc as trading_pb2_grpc from trading import trading_types_pb2 outbound = Queue() def requests(): while True: yield outbound.get() outbound.put( trading_pb2.StreamEventsRequest( subscribe=trading_pb2.Subscribe( trading_account_id= "YOUR_TRADING_ACCOUNT_ID", environment=trading_types_pb2.BROKER_ENVIRONMENT_PAPER, subscriptions=[ trading_pb2.EVENT_TYPE_BROKER_ORDER, trading_pb2.EVENT_TYPE_BROKER_POSITION, ], ) ) ) credentials = grpc.ssl_channel_credentials() with grpc.secure_channel( "stream.anthid.com:443", credentials, ) as channel: stub = trading_pb2_grpc.TradingServiceStub( channel, ) stream = stub.StreamEvents( requests(), metadata=( ("x-api-key", "YOUR_API_KEY"), ), ) for event in stream: print(event) ``` ## Handling Disconnects Applications should automatically reconnect when a stream is interrupted. After reconnecting: 1. Establish a new stream. 2. Re-authenticate using a Bearer access token or API key. 3. Re-subscribe to required trading accounts. 4. Resume event processing. ## Related Resources * Streaming Overview * Trading Accounts * Orders * Positions * Ledger * Intents # Rust Source: https://docs.anthid.com/api-reference/stream/rust Connect to Anthid's Streaming API using Rust and Tonic. Anthid provides Protocol Buffer definitions that can be used to generate Rust clients with Tonic. This guide demonstrates connecting to the Streaming API and receiving real-time broker order and position updates. ## Prerequisites Add the required dependencies. ```toml theme={null} [dependencies] tokio = { version = "1", features = ["full"] } tokio-stream = "0.1" tonic = { version = "0.14", features = ["tls-webpki-roots"] } tonic-prost = "0.14" prost = "0.14" prost-types = "0.14" ``` ## Generate Rust Types Install the Protocol Buffer compiler, then clone the Anthid definitions. ```bash theme={null} git clone https://github.com/anthid-labs/proto.git anthid-proto ``` Example `build.rs`: ```rust theme={null} fn main() -> Result<(), Box> { tonic_prost_build::configure() .build_client(true) .build_server(false) .compile_protos( &[ "anthid-proto/trading/trading_service.proto", ], &["anthid-proto"], )?; Ok(()) } ``` Example `Cargo.toml` build dependency: ```toml theme={null} [build-dependencies] tonic-prost-build = "0.14" ``` ## Authentication Anthid authenticates streaming requests with either a Bearer access token or the `x-api-key` metadata header. This guide uses an API key. ```rust theme={null} use tonic::metadata::MetadataValue; request.metadata_mut().insert( "x-api-key", MetadataValue::try_from("YOUR_API_KEY")?, ); ``` ## Subscribe to Events The `SubscribeEvents` endpoint provides a server-streaming interface suitable for most applications. ```rust theme={null} use tonic::{ metadata::MetadataValue, Request, }; pub mod trading { tonic::include_proto!("trading"); } use trading::{ trading_service_client::TradingServiceClient, EventType, Subscribe, }; #[tokio::main] async fn main() -> Result<(), Box> { let mut client = TradingServiceClient::connect( "https://stream.anthid.com", ) .await?; let mut request = Request::new( Subscribe { trading_account_id: "YOUR_TRADING_ACCOUNT_ID" .to_string(), environment: trading::BrokerEnvironment::Paper as i32, subscriptions: vec![ EventType::BrokerOrder as i32, EventType::BrokerPosition as i32, ], ..Default::default() }, ); request.metadata_mut().insert( "x-api-key", MetadataValue::try_from( "YOUR_API_KEY", )?, ); let mut stream = client .subscribe_events(request) .await? .into_inner(); while let Some(event) = stream.message().await? { println!("{event:?}"); } Ok(()) } ``` `environment` is required: set it to `trading::BrokerEnvironment::Paper as i32` or `trading::BrokerEnvironment::Live as i32` to match the account, because `Default::default()` leaves it unspecified and the server refuses that. Set `order_delivery: trading::OrderDelivery::Primed as i32` on `Subscribe` to receive retained order snapshots first. `Default::default()` leaves instrument filters empty and order delivery live-only. Positions always prime retained state. Wait for the corresponding `Primed` event before treating initial delivery as complete. Terminal orders and zero-quantity positions expire from retained stream state after one hour. Rebuild a fresh snapshot set on reconnect so an old cache does not retain entries that no longer replay. See [Rebuilding a cached view](/api-reference/stream/overview#rebuilding-a-cached-view). ## Handling Events Each `TradingEvent` contains a single event variant. ```rust theme={null} while let Some(event) = stream.message().await? { match event.event { Some( trading::trading_event::Event::Heartbeat(_), ) => { println!("heartbeat"); } Some( trading::trading_event::Event::Status( status, ), ) => { println!( "status: {}", status.message ); } Some( trading::trading_event::Event::Error( error, ), ) => { println!( "error: {}", error.message ); } Some( trading::trading_event::Event::BrokerOrder( order, ), ) => { println!( "order: {:?}", order ); } Some( trading::trading_event::Event::BrokerPosition( position, ), ) => { println!( "position: {:?}", position ); } Some(trading::trading_event::Event::Primed(primed)) => { println!("initial state complete: {}", primed.event_type); } Some(trading::trading_event::Event::BrokerTrade(trade)) => { println!("trade: {trade:?}"); } Some(trading::trading_event::Event::IntentAction(action)) => { println!("intent action: {action:?}"); } None => {} } } ``` ## Bidirectional Streaming Backend and native applications may use `StreamEvents` when subscriptions need to be modified over an existing connection. ```rust theme={null} use tokio_stream::iter; use tonic::Request; use trading::{ EventType, StreamEventsRequest, Subscribe, }; let request_stream = iter(vec![ StreamEventsRequest { msg: Some( trading::stream_events_request::Msg::Subscribe( Subscribe { trading_account_id: "YOUR_TRADING_ACCOUNT_ID" .to_string(), environment: trading::BrokerEnvironment::Paper as i32, subscriptions: vec![ EventType::BrokerOrder as i32, EventType::BrokerPosition as i32, ], ..Default::default() }, ), ), }, ]); let mut request = Request::new(request_stream); request.metadata_mut().insert( "x-api-key", MetadataValue::try_from( "YOUR_API_KEY", )?, ); let response = client .stream_events(request) .await?; let mut stream = response.into_inner(); ``` ## Handling Disconnects Applications should automatically reconnect when a stream closes unexpectedly. After reconnecting: 1. Create a new stream. 2. Authenticate using a Bearer access token or API key. 3. Re-subscribe to required trading accounts. 4. Resume event processing. ## Related Resources * Streaming Overview * Python Guide * Trading Accounts * Orders * Positions * Ledger * Intents # Delete symbol control Source: https://docs.anthid.com/api-reference/symbol-manual-controls/delete-symbol-control /api-reference/controls.openapi.json delete /v1/controls/accounts/{account_id}/symbols/{symbol} Deletes manual controls for a symbol on a trading account in the caller's organization. Requires Controls Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Get symbol control Source: https://docs.anthid.com/api-reference/symbol-manual-controls/get-symbol-control /api-reference/controls.openapi.json get /v1/controls/accounts/{account_id}/symbols/{symbol} Returns manual controls for a symbol on a trading account. Users and API keys require Controls Premium and control read access. Services need `control:read` and `x-organization-id`. The organization must own the account. # Patch symbol control Source: https://docs.anthid.com/api-reference/symbol-manual-controls/patch-symbol-control /api-reference/controls.openapi.json patch /v1/controls/accounts/{account_id}/symbols/{symbol} Creates or patches manual controls for a symbol on a trading account. Omitted and null fields are unchanged; `clear` removes named fields. Requires Controls Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Upsert symbol control Source: https://docs.anthid.com/api-reference/symbol-manual-controls/upsert-symbol-control /api-reference/controls.openapi.json put /v1/controls/accounts/{account_id}/symbols/{symbol} Creates or replaces manual controls for a symbol on a trading account in the caller's organization. Omitted optional fields are cleared. Requires Controls Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Delete symbol risk control Source: https://docs.anthid.com/api-reference/symbol-risk-controls/delete-symbol-risk-control /api-reference/controls.openapi.json delete /v1/controls/accounts/{account_id}/symbols/{symbol}/risk Deletes risk limits for a symbol on a trading account in the caller's organization. Requires Controls Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Get symbol risk control Source: https://docs.anthid.com/api-reference/symbol-risk-controls/get-symbol-risk-control /api-reference/controls.openapi.json get /v1/controls/accounts/{account_id}/symbols/{symbol}/risk Returns risk limits for a symbol on a trading account. Users and API keys require Controls Premium and control read access. Services need `control:read` and `x-organization-id`. The organization must own the account. # Patch symbol risk control Source: https://docs.anthid.com/api-reference/symbol-risk-controls/patch-symbol-risk-control /api-reference/controls.openapi.json patch /v1/controls/accounts/{account_id}/symbols/{symbol}/risk Creates or patches risk limits for a symbol on a trading account. Omitted and null fields are unchanged; `clear` removes named fields. Requires Controls Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Upsert symbol risk control Source: https://docs.anthid.com/api-reference/symbol-risk-controls/upsert-symbol-risk-control /api-reference/controls.openapi.json put /v1/controls/accounts/{account_id}/symbols/{symbol}/risk Creates or replaces risk limits for a symbol on a trading account in the caller's organization. Omitted optional fields are cleared. Requires Controls Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Delete symbol time control Source: https://docs.anthid.com/api-reference/symbol-time-controls/delete-symbol-time-control /api-reference/controls.openapi.json delete /v1/controls/accounts/{account_id}/symbols/{symbol}/time Deletes time-window controls for a symbol on a trading account in the caller's organization. Requires Controls Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Get symbol time control Source: https://docs.anthid.com/api-reference/symbol-time-controls/get-symbol-time-control /api-reference/controls.openapi.json get /v1/controls/accounts/{account_id}/symbols/{symbol}/time Returns time-window controls for a symbol on a trading account. Users and API keys require Controls Premium and control read access. Services need `control:read` and `x-organization-id`. The organization must own the account. # Patch symbol time control Source: https://docs.anthid.com/api-reference/symbol-time-controls/patch-symbol-time-control /api-reference/controls.openapi.json patch /v1/controls/accounts/{account_id}/symbols/{symbol}/time Creates or patches time-window controls for a symbol on a trading account. Omitted and null fields are unchanged; `clear` removes named fields. Requires Controls Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Upsert symbol time control Source: https://docs.anthid.com/api-reference/symbol-time-controls/upsert-symbol-time-control /api-reference/controls.openapi.json put /v1/controls/accounts/{account_id}/symbols/{symbol}/time Creates or replaces time-window controls for a symbol on a trading account in the caller's organization. Omitted optional fields are cleared. Requires Controls Premium. Users need an organization admin role; API keys need control edit permission for the account's environment. # Trading infrastructure your firm does not have to build Source: https://docs.anthid.com/index Connect broker accounts, put controls in front of every order, and keep a durable record of what was instructed and what happened. ![Anthid dashboard preview](https://mintcdn.com/dallinwrightanthidcom/b__zdJQ9aOhLz_f-/images/hero.png?fit=max\&auto=format\&n=b__zdJQ9aOhLz_f-\&q=85\&s=db0248e5b5f28ab0cb671ea3cca30996) Anthid sits between your trading systems and your brokers. It carries orders to the broker, enforces the limits you set before they get there, reports back what happened, and keeps a durable record of all of it. That means one connection to maintain instead of one per broker, and one place to answer what was instructed, by whom, and what came of it. > **[Start with the introduction](/pages/intro/introduction)** > > How Anthid fits between your systems and your brokers, in about five minutes of reading. ## What Anthid provides > **[Execution](/pages/concepts/intents)** > > An intent is recorded before the broker is contacted, dispatch is reported separately from fill state, and every amendment is kept as history. The instruction reads the same whichever connected broker it goes to. > **[Pre-trade controls](/pages/controls/overview)** > > Kill switches, size limits, and trading windows at the organization, account, or symbol level. Applied before an order reaches a broker. > **[A durable record](/pages/about/compliance)** > > Every instruction, every refusal, and every control change recorded against the credential behind it, and kept for six years. > **[Live updates and history](/pages/concepts/orders)** > > Order and position activity as it happens, plus queryable history for reporting and reconciliation. ## How it works **1. Connect a trading account** Link a supported broker account through the dashboard. Anthid maintains the connection and reports its health. **2. Set your limits** Configure kill switches, order size limits, and trading windows. Changes apply without redeploying anything. **3. Send orders** Your systems submit orders to one API. Anthid checks them against your limits, routes them to the connected broker, and records what was asked for. **4. See what happened** Order and position updates arrive as the broker reports them, and the full history stays queryable for reporting, reconciliation, and review. ## Evaluating Anthid > **[What we record](/pages/about/compliance)** > > What is captured on every order, how long it is kept, and what that means for a registered firm. > **[How credentials are held](/pages/about/security)** > > How broker credentials are protected and how one client's data is kept out of another's. > **[What each broker supports](/pages/brokers/supported-order-types)** > > Order types, routes, and time-in-force values, broker by broker, with the gaps stated. > [!NOTE] > > The Lightspeed integration is currently in beta: usable in production and exercised end to end, still being hardened, and carrying no service level commitment. See [Integration stability](/pages/brokers/overview#integration-stability). ## For your engineers > **[Quickstart](/pages/intro/quickstart)** > > Connect a paper account and send a first order. > **[API reference](/api-reference/introduction)** > > Authentication, endpoints, request and response shapes, and the streaming API. ## Need help? > **[Community](/pages/community/overview)** > > Ask questions and share feedback with the team and other Anthid users. > **[Talk to us](mailto:sales@anthid.com)** > > Vendor reviews, plan questions, and anything a documentation page does not answer. # Compliance Source: https://docs.anthid.com/pages/about/compliance How Anthid records and preserves order instructions, control changes, and refusals, and what that means for broker-dealer and registered adviser clients Anthid is trading infrastructure, not a broker-dealer. We do not take custody of customer assets, we do not make recommendations, and we do not exercise discretion over any order. We act on instruction: every order that reaches a broker through Anthid originates from an authenticated client request, and we keep a durable record of that request. For clients who are themselves registered, whether as a broker-dealer or as a registered investment adviser, that recordkeeping matters, because your own obligations follow the order flow even when the infrastructure is ours. This page describes what we record, how it is protected, and how long we keep it. > **Anthid does not assume your obligations** > > Nothing on this page transfers a regulatory obligation from your firm to Anthid. Recordkeeping, supervision, and best execution remain yours. What Anthid provides is infrastructure designed so those obligations are practical to meet. Confirm your specific requirements with your compliance counsel. ## Record retention SEC Rule 17a-3 describes the records a broker-dealer must make. Rule 17a-4 describes how long those records must be preserved. The periods differ by record type: memoranda of brokerage orders under 17a-3(a)(6) are three-year records under 17a-4(b)(1), while blotters (the records of original entry under 17a-3(a)(1)) are six-year records under 17a-4(a). The order instruction stream Anthid keeps is source data for both. Rather than classify each record individually, **Anthid retains the full order instruction stream for six years**, satisfying the longer period. Retention is enforced by policy at the database, not by convention or by a cleanup job. Records are not deleted early and are not deleted on request. > **Two years is an accessibility window, not a retention period** > > Rule 17a-4 requires the first two years of a record's life to be in an easily accessible place. That is a requirement about *where* a record lives, not *whether* it still exists. Anthid keeps records for the full six years. ## What gets recorded Every order instruction is written to an append-only event stream before Anthid acknowledges it. Each record carries: | Field | What it establishes | | - | - | | Resolved order payload | The exact instruction as Anthid understood it: symbol, side, type, quantity, limit and stop prices, time in force, and routing strategy | | Organization and account | Which client instructed the order, and which trading account it was for | | Action type and sequence | Whether the instruction was a create, replace, or cancel, and its ordered position in that intent's lifecycle | | Ingress timestamp | When the instruction arrived at the Anthid edge, recorded independently of when it was written to storage | | Write timestamp | When the record was committed | | Intent identifier | The intent the action belongs to, carried through unchanged; yours if you named it on the create | | Credential | Which of your credentials instructed the order, and of what kind: an API key, a signed-in user, or an Anthid service acting on your behalf | | Request identifier | Correlates the record to the single request that produced it | | Client address | The calling address as reported by the edge proxy | Replaces and cancels do not overwrite the original instruction. They are appended as new sequenced actions against the same intent, so the full history of what was instructed, and in what order, is reconstructible. Attribution is taken from the request that produced each action, never inherited. A replace or cancel carries forward the payload of the order it commands, but records the credential that sent *it*, so one credential amending or cancelling what another one placed is visible rather than hidden. > **Attribution cannot be added retroactively** > > Anthid records the credential on every action because there is no way to reconstruct it later. A record that says only which organization instructed an order cannot answer which of your systems sent it, or whether it was sent with a key you have since revoked. Those are the questions a disputed order actually turns on. ## Recorded before acknowledged An order instruction is committed to durable storage in the same database transaction that queues it for execution, and the client receives a response only after that transaction commits. This ordering is deliberate. It means no order can reach a broker without a corresponding record, because the record and the execution request are written atomically. There is no window in which an instruction is in flight but unrecorded. ## Immutability The operational intent table rejects updates through a database trigger. Replacements and cancellations append new actions rather than rewriting earlier instructions. Completed action chains are copied to the ledger, and the cleanup worker removes their operational rows only after ledger confirmation, a grace period, and checks that dispatch is complete. The update trigger does not prohibit deletes; archive cleanup relies on those worker checks. Corrections are not edits. Any subsequent action against an intent is a new sequenced record, leaving the original in place. ## Execution records The instruction record answers what a client asked for. A separate append-only stream records what happened next: each state transition as the order moved to the broker, the broker's own order identifiers, and any rejection or error returned upstream. The two streams are joinable, so an order can be reconstructed end to end, from the request that arrived at the Anthid edge, through the routing decision, to the broker's acknowledgment. ## Pre-trade controls Anthid evaluates every create instruction against your configured controls before it is accepted. Organization, account, and symbol-level controls, including risk and time-based restrictions, are resolved into an effective policy and applied at submission. This gives supervisory constraints a place to live in the infrastructure itself rather than only in the application calling it. See [Controls](/pages/controls/overview) for configuration. ## Changes to controls A control decides whether an order is permitted to exist, which makes changing one a supervisory act in its own right. Every control change is appended to its own record stream, at all three levels of the hierarchy. The reason this stream exists is that current state alone cannot answer a review. Deleting a control used to erase the only copy of it, so nothing could say who disabled an account, when, or under which credential, and that is the half of a dispute a review actually turns on. Each record carries: | Field | What it establishes | | - | - | | Scope and kind | Which level the control sits at, and whether it is a manual, risk, or time control | | Sequence | The change's ordered position in that control's history | | Fields set | The values the change asserted, exactly as you supplied them | | Fields cleared | The limits the change removed, each named explicitly | | Credential | Which of your credentials made the change, and of what kind | | Request identifier and idempotency key | Correlates the change to the request that carried it | | Client address | The calling address as reported by the edge proxy | | Arrival and write timestamps | When the request reached the edge, and when the record committed | Setting a value and removing one are recorded as different acts, and a null never clears a limit. Removing one costs an explicit name. On a risk control, the difference between leaving `max_order_size` alone and removing the `max_order_size` limit is the difference between an order being blocked and an order being sent, so a client that serialises its unset fields as null cannot strip every limit it never mentioned. The current state of each control stays a directly readable row, which keeps the order path a key lookup rather than a fold over history. Each of those rows names the change that produced it, so a disagreement between a control and its history is detectable rather than silent. Control history is retained for the same six years as the order records. A control is what allowed or blocked an order, so it belongs to the same record as the order it acted on. Expiring it sooner would leave years in which an instruction and its execution survive but the reason the platform permitted it does not. > **A repeated delete is still recorded** > > Deleting a control that is already gone still appends a record. The second attempt was a real request made by a real principal, and a stream that silently swallowed it would be less truthful than one that shows it. ## What was refused, and why A record of what Anthid accepted only tells you half of what a supervisory review asks for. The other half is what it stopped. Refused submissions are recorded on their own append-only stream, carrying the same attribution as an accepted order and the submission exactly as it arrived. Three kinds of refusal are recorded: | Stage | What it means | | - | - | | `CONTROLS` | Your own configured trading controls stopped the order. The record carries the specific controls that triggered, which is the same detail the caller received. | | `ENTITLEMENT` | The caller was permitted, but the plan does not cover what was attempted, such as live trading on a live account. | | `AUTHORIZATION` | The caller lacked permission to submit, or named a trading account belonging to a different organization. | That last case is worth stating plainly: **an attempt to submit against an account your organization does not own is refused and recorded.** The caller is told the account was not found, which does not confirm whether the identifier exists elsewhere, and the attempt is preserved with the credential behind it. A credential reaching for accounts it should not is exactly the pattern a review needs to be able to see. Recording a refusal never affects the outcome of the request. The submission is refused on its own merits, and the record is written independently of the transaction the order would have used, so a refusal cannot be lost to the same rollback that discards the order. Refusals are queryable through the API alongside accepted orders, filterable by account, stage, and a trailing window in days, and retained for the same six years. > **Not every rejected request is recorded here** > > This stream records decisions Anthid made about whether a caller may trade. It deliberately does not record malformed requests. An unparseable order or an unknown identifier is a client error rather than a refusal, and mixing the two would bury the supervisory signal in ordinary integration noise. ## Changes to connections and credentials Connecting a broker account, disabling one, rotating a key, and deleting a connection are recorded the same way, on append-only streams carrying the same attribution as an order. A connection's current state is derived from those events rather than held as a row that each change overwrites, so its history is not lost to its latest state. Deletion is a tombstone on the stream rather than the removal of a row. The secret itself is destroyed, while the record that the connection existed, and of who removed it and when, remains. The same separation applies to organizations. Deletion is recorded distinctly from an administrative suspension, so a deleted organization and a temporarily disabled one are no longer indistinguishable after the fact. ## Data protection Order records are scoped to the organization that created them, and that scoping is enforced by row-level security policies in the database rather than by application code alone. Broker credentials are held encrypted, separately from trading workflows, and are never stored in plaintext by Anthid. See [Security](/pages/about/security) for the platform's security model, including how credentials are encrypted, how tenant isolation is enforced, and what is captured about the caller behind every change. ## Questions Compliance requirements vary by firm, registration type, and business model. If your firm needs specifics about Anthid's recordkeeping to complete a vendor review or satisfy a supervisory requirement, contact [support@anthid.com](mailto:support@anthid.com). # Security Source: https://docs.anthid.com/pages/about/security How Anthid protects broker credentials, keeps one client's data out of another's, and attributes every change to the credential behind it Anthid holds the credentials your systems use to reach a broker, and sits between those systems and the orders they send. That places two obligations on the platform: the secrets have to be protected, and every change to them has to be attributable. This page describes how credentials are stored and destroyed, how one client's data is kept out of another client's reads, and what is recorded when something changes. It is written to be read during a vendor review. ## Credential storage Broker credentials are encrypted before they are stored, and the key that encrypts them is itself encrypted under a master key held outside the database. The practical consequence is that a stored credential record contains no usable key material. Reading the database, in full, does not yield a broker credential. The unencrypted key exists only for the instant it is being used. The encrypted credentials are held in WorkOS Vault. Anthid's own database stores only a pointer to the vault entry, never the secret itself. > **An encrypted credential is bound to the client it belongs to** > > The organization, broker, and environment a credential was created for are cryptographically bound into the encrypted value. If any of the three differ when it is decrypted, decryption fails outright. A credential record copied into another client's context does not decrypt into the wrong client. It does not decrypt at all. Credentials can be rotated whenever your team needs to, which gives you a practical path to respond to policy changes, broker-side credential updates, or staff access changes without redesigning the integration. ## Credential deletion Deleting a credential means two things must happen: the credential must stop being usable, and the secret must be destroyed in the vault. The two can fail independently, so Anthid does them in a defined order. The credential is marked deleted, and the instruction to destroy the secret is committed, in a single durable step. The destruction itself happens immediately afterwards. Committing the instruction rather than simply firing it off is what makes a deletion survive a failure between the two: if anything interrupts the process, the outstanding destruction is picked up and completed rather than silently abandoned. A repeated or retried delete does not queue a second destruction. The record of the deletion outlives the secret. Who deleted the credential, when, and under which of your credentials remains on the account's history after the secret it pointed at is gone. ## Keeping clients separate Every record that belongs to a client is tagged with that client's organization, and the separation is enforced by the database itself rather than by the application asking nicely. Every read declares which client it is acting for, and the database refuses to return rows belonging to anyone else. A read that fails to declare a client returns nothing rather than everything. That default is the important half: the failure mode of a bug in this area is an empty result, not another client's data. The same rule applies to Anthid's own administrative access. There is no privileged path that quietly bypasses it. ### Records that cannot be rewritten The history of trading accounts and broker credentials is append-only. Correcting something means adding a new entry, not editing an old one, and the database rejects any attempt to modify or remove an existing record. That restriction is enforced in three independent ways at the database level, so defeating it means defeating all three rather than finding one gap. Anthid's own application has no ability to alter a recorded entry, and neither does an ordinary database session. Internal read access for support and operations runs through a separate, read-only identity that cannot make changes and cannot be used to log in interactively. ## What is recorded when something changes Connecting a broker account, disabling one, rotating a key, and deleting a connection are all acts somebody performed. Each is recorded, and each carries the caller behind it. | Recorded | What it establishes | | - | - | | Who | Whether an API key, a signed-in user, or an Anthid service made the change, and which one | | Request identifier | Ties the change to the single request that produced it, and to the logs from serving it | | Your idempotency key | Recorded exactly as you sent it | | Client address | The calling address as observed at the network edge | | Arrival time | When the request reached Anthid, recorded separately from when the record was written | These values are captured before any processing begins. They are therefore identical across every record a single request produces, and they exist even for requests that fail before they get anywhere. Attribution is taken from the request every time, never inherited from the thing being changed. One credential disabling what a different credential created is visible rather than hidden. > **A new kind of caller cannot be silently mis-recorded** > > Anthid cannot add a new class of caller without deciding, explicitly, how it is recorded. The system refuses to build otherwise. The alternative, a plausible-looking default written into a record whose entire value is that it can be trusted, is the failure this design exists to prevent. ## About the recorded address The client address is taken from what Anthid's network edge observed when it accepted the connection. It is not taken from the caller-supplied forwarding header that is conventionally used for this, because a caller can put anything it likes in that header. There is no fallback. When a request does not arrive through the edge, which is what an internal call between Anthid's own services looks like, no address is recorded at all. Once written, a blank and a caller-invented string are indistinguishable, and these values are used as evidence, so recording nothing is the honest outcome. > **What this value is worth** > > A recorded address is only as trustworthy as the guarantee that traffic cannot reach Anthid except through its network edge. This is a property of how the platform is deployed, and we state it as such rather than overclaiming. ## Revocation and deletion An API client records *when* it was revoked, not merely *that* it was. A yes-or-no flag cannot answer a caller retrying a revocation it is unsure landed, and it cannot answer the question an incident review asks first, which is when the credential stopped working. The two values cannot disagree. Deleting an organization is recorded separately from disabling one. The two previously shared a single state, so nothing distinguished an organization that had been deleted from one an administrator had temporarily switched off. A deleted organization is also disabled, but a disabled organization is not necessarily deleted. Trading accounts follow the same principle. Deleting one marks it deleted rather than erasing it, so the account's history stays intact and the deletion itself is attributable. ## Trading controls Security works alongside Anthid's trading controls. Organization, account, and symbol-level guardrails limit where and when trading can occur, and can stop activity outright when your team needs to pause quickly. Changes to those controls are themselves recorded with the same attribution described above. See [Controls](/pages/controls/overview) for configuration, and [Compliance](/pages/about/compliance) for what is recorded about orders and how long it is kept. ## Questions If your firm needs specifics about Anthid's security model to complete a vendor review, contact [support@anthid.com](mailto:support@anthid.com). We can go a level deeper than this page does, including on the encryption scheme, the database isolation model, and our deployment boundary. # How to add a second broker without a rewrite Source: https://docs.anthid.com/pages/blog/articles/add-a-second-broker-without-a-rewrite Why the second broker breaks a home-built execution layer, why it is a setting when the layer is Anthid's, and a rollout plan that costs no code changes The second broker is where home-built execution layers go to die. The first integration feels like an achievement. The second one reveals that what you built was never a layer. It was a client, and everything above it quietly took the client's shape. ## Why the second broker hurts so much Your first broker shaped your whole codebase without you noticing. Order payloads match its formats. Error handling matches its error codes. Your order state machine mirrors its lifecycle, your position tracking mirrors its account model, and your event handlers expect its websocket messages. Broker two disagrees with all of it. Different field names, different order states, different ideas about what a partial fill looks like, different authentication, different rate limits. So you do what every team does: you start writing an adapter layer. One interface, two implementations, and a growing pile of special cases. Congratulations, you are now an infrastructure company. The adapter layer needs tests, monitoring, and maintenance every time either broker changes anything, and it still only covers two brokers. ## The broker as a setting Anthid takes a different path, and it starts above the broker rather than at it. Your strategy talks to one execution layer: you describe the outcome you want as an [intent](/pages/concepts/intents), in Anthid's own terms, and Anthid records it before contacting any broker, checks it against your [controls](/pages/controls/overview), routes it, streams back what happened, and keeps the record. The broker's dialect is something the layer translates into at routing time, not something your code ever sees. That is why adding a second broker is a config change, not a rewrite. You connect the new account, and your strategy code stays exactly as it was. It never knew which broker it was talking to in the first place, so it has nothing to relearn. [Lightspeed](/pages/brokers/lightspeed-connect) is connected today, with [more on the roadmap](/pages/community/roadmap), and each new integration arrives behind the same interface. ## What stays the same Everything you actually care about. Your strategy logic is untouched. Your event handling is untouched, because every broker's events arrive in the same normalized stream regardless of where the fill happened. Your records stay coherent too: the [durable ledger](/api-reference/ledger/overview) keeps intents, orders, and positions from every connected broker in one place, with dispatch state and fill state reported the same way for each, so "what happened yesterday" has a single answer instead of two logs in two formats. Your risk controls also carry over. Trading windows and order size limits are enforced by the platform before any order leaves, and they apply no matter which broker is on the other end. You do not rebuild your safety net per broker, and every refusal is recorded the same way whichever account it was headed for. > [!WARNING] > > Maximum daily loss and maximum position size can be configured today, but they are stored rather than enforced: the pre-trade evaluation checks order size alone. Keep those checks in your own application until [Controls Overview](/pages/controls/overview) says otherwise. ## What to check before you flip the switch One honest caveat. Brokers differ in what they accept, and Anthid does not paper over that: an unsupported combination of order type, destination, or time in force is rejected rather than routed. Lightspeed accepts the four intent order types, `DAY`, `SMART` routing, and its direct exchange routes today. Each integration publishes its own support matrix, and the choices it shares with the broker you already use are the portable set. Everything else is broker-specific. So the work is a review, not a rewrite: read your intents for values outside that portable set, and decide what the second broker should do instead. The matrices are in [Supported Order Types](/pages/brokers/supported-order-types), [Supported Time In Force](/pages/brokers/supported-time-in-force), and [Supported Destinations](/pages/brokers/supported-destinations). ## A realistic rollout plan Here is how this looks once the broker you want is connected. Start where you already are, with your strategy running against your first broker. Connect the second broker and point it at paper trading first, since Anthid's free tier includes paper trading with no credit card and no sales call. Run the same strategy against both brokers side by side for a few sessions and compare fills, latency, and behavior in the normalized event stream, which reads the same for both. When you trust what you see, flip the second broker to live. Total code changes along the way: none. Compare that to the home-built route, where the same rollout starts with a month of adapter work before you can even begin testing. > [!NOTE] > > Live broker accounts need a Starter or Professional plan, and your tier also caps how many broker connections an organization can create. See [Availability](/api-reference/accounts/overview#availability). ## The bigger point Broker choice should be a business decision, not an engineering project. Maybe you want better fills, redundancy during outages, or an asset class your first broker does not cover. Those are good reasons to add a broker, and none of them deserve a quarter of engineering time. When the execution layer is above the broker rather than built around it, the decision shrinks to the size it always should have been: a setting. > **[Trading accounts](/api-reference/accounts/overview)** > > How a connected broker account is represented, and how credentials are stored. # Anthid vs Lightspeed: which one should you build on? Source: https://docs.anthid.com/pages/blog/articles/anthid-vs-lightspeed Anthid is the execution and risk layer your strategy talks to, and Lightspeed is a brokerage it routes to, so the question is whether to build your own layer on Lightspeed Connect or put Anthid in front of it Anthid is the execution and risk layer your strategy talks to. Lightspeed is a brokerage it routes to, and a serious one. So the real choice is not between two providers. It is between building your own execution layer against Lightspeed Connect or running your strategy through Anthid's with [Lightspeed behind it](/pages/brokers/lightspeed-connect). ## What Lightspeed gives you Lightspeed built its name on active and professional traders, and it shows. Lightspeed publishes equities and ETFs from \$0.001 per share, options from \$0.20 per contract, futures through its eFutures subsidiary, extended hours access, and smart routing aimed at execution quality. Its API product, Lightspeed Connect, comes with a certification environment for paper trading and market data through Polygon.io. Expect a margin account minimum around \$30,000, which tells you who this broker is for: people trading real size who care about fills. That caliber of execution is exactly why Anthid routes to it. > [!NOTE] > > Lightspeed sets those terms and can change them, so confirm the current rates and minimums with Lightspeed rather than with us. What Anthid routes to Lightspeed today is narrower than Lightspeed's full product: four order types, `DAY` time in force, and smart or direct exchange routing. Extended hours is not routable through the intent API on any broker yet. See [Supported Order Types](/pages/brokers/supported-order-types), [Supported Time In Force](/pages/brokers/supported-time-in-force), and [Supported Destinations](/pages/brokers/supported-destinations). ## What a broker API leaves on your plate Lightspeed Connect gets your orders to the market. What it does not do is run the layer around them, because that is not a broker's job. Once you are live, you own the machinery around every order: a record that the instruction existed before the broker answered, catching rejections, surviving dropped connections without losing fills, and reconciling your book against the broker when the two disagree. You own your safety rails too. A trading window or an order size limit only exists if you wrote it, and it only saves you if it fires in time. And everything you write bakes in Lightspeed's formats, order states, and quirks. That layer is loyal to one broker. It will not follow you anywhere else. ## What the layer does Anthid sits between your strategy and the broker and runs that layer as a service. You describe the outcome you want as an [intent](/pages/concepts/intents) instead of babysitting raw orders. Anthid records it in an append-only log in the same transaction that queues it, so no order reaches Lightspeed without a record. Dispatch state and fill state are reported separately, so sent is never mistaken for filled, and every replace and cancel is kept as sequenced history. Events from Lightspeed arrive in the same normalized stream as events from any other connected broker and land in a [durable ledger](/api-reference/ledger/overview) kept for six years, so your records have one shape and one home. [Risk controls](/pages/controls/overview) run before an order leaves: trading windows and order size limits enforced by the platform, not by a script you hope you tested, and every refusal recorded. > [!WARNING] > > Maximum daily loss and maximum position size can be configured today, but they are stored rather than enforced: the pre-trade evaluation checks order size alone. Keep those checks in your own application until [Controls Overview](/pages/controls/overview) says otherwise. The platform runs at 99.99% uptime with median order acknowledgement of 42ms, and someone else carries the pager. > [!NOTE] > > Those numbers describe how the platform has been running, not a commitment. The connected broker integration is in beta, and a broker integration carries no service level commitment. See [Service levels](/pages/brokers/overview#service-levels). ## The part people miss Here is the quiet advantage of putting Anthid in front of Lightspeed: the broker becomes a setting. The instruction, the controls, the stream, and the record are the same whichever connected account is behind them, so you stop having to choose one broker forever. Lightspeed is the broker connected today, and further integrations are on the [roadmap](/pages/community/roadmap). When one lands, adding it or running both at once for redundancy is a config change. A home-built layer makes that a rewrite. Anthid makes it a line of configuration, and your strategy code never notices. > [!NOTE] > > One thing to check before you point a strategy at Lightspeed: it honors `DAY` today, and `GOOD_TILL_CANCEL`, `IMMEDIATE_OR_CANCEL`, and the auction instructions are not routed to it. A strategy built on those values needs them reviewed first, because Anthid rejects an unsupported combination rather than routing it. See [How to add a second broker without a rewrite](/pages/blog/articles/add-a-second-broker-without-a-rewrite). ## So which should you pick? If you have the engineering bench to build and staff an execution layer, and you are certain Lightspeed is your broker forever, going direct is a defensible choice. But if you want Lightspeed's execution without building a trading operations team around it, put Anthid in front. You keep the broker's strengths and shed the layer. The test costs nothing. Anthid's free tier includes paper trading with no credit card and no sales call, so you can prove out the strategy first and bring Lightspeed's account minimum into the picture only when you are ready to trade real size. > **[Lightspeed Connect integration](/pages/brokers/lightspeed-connect)** > > What the Lightspeed integration supports today, including its broker limits. Controls apply to new `CREATE` instructions. They do not reevaluate replacements or cancel resting orders. HTTP create retries are not deduplicated; reconcile an uncertain submission before retrying. See [Intents](/api-reference/intents/overview) for the current contract. # Anthid is more than an EMS/OMS Source: https://docs.anthid.com/pages/blog/articles/beyond-ems-oms How Anthid brings broker connectivity, durable instructions, trading controls, live operations, and execution history into one managed platform. Anthid brings order and execution management into a broader platform for running a trading operation. It connects broker accounts, records instructions, enforces configured controls, exposes live activity, and gives your team a shared record to work from. That scope matters as soon as trading involves more than one application or one person. A strategy needs to submit an order. An operator needs to know whether it reached the broker. An administrator needs to restrict what the strategy can do. Later, someone needs to explain why the order was placed, amended, or refused. Anthid connects those jobs through the same accounts, instructions, and history. EMS and OMS are useful starting points for understanding the product. The fuller description is **managed trading infrastructure**: the services and operational tools between the applications making trading decisions and the brokers carrying them out. ## The order is part of a longer workflow Consider a team running a strategy against a connected account. The strategy submits a limit order, changes its price, and eventually asks to cancel the remainder. Meanwhile, an operator watches fills and positions, and an administrator changes the account's trading window. The team needs to answer several questions about the same activity: | Question | Where Anthid answers it | | - | - | | Is the broker account connected? | Trading accounts and connection status | | What did the strategy ask for, including its amendments? | The intent and its action chain | | Was the instruction allowed under the configured controls? | Pre-trade evaluation and refusal records | | Did Anthid dispatch it, and what did the broker do? | Separate intent dispatch state and broker order outcome | | What is happening now? | Live intent, order, and position streams | | How did the activity behave over the session? | Ledger queries and order and exposure analytics | Each answer is useful by itself. Their connection is what makes Anthid a platform. You can follow an instruction from the application that sent it into the operational view your team uses, then into the history you investigate afterwards. ## An instruction has a life before the broker responds Anthid models what you ask for as an [intent](/pages/concepts/intents). After the submission passes validation, authorization, and the applicable controls, the accepted instruction is committed in the same transaction that queues its execution. That gives the instruction a durable identity before the broker answers. If the connection fails or the broker refuses the order, you can still inspect what Anthid accepted and how far it got. A replace or cancel adds a sequenced action to the same intent. The original request remains part of the history. Each action records the credential behind that action, so a person cancelling an order submitted by a strategy does not erase the distinction between them. Your application can mint the intent's identifier itself and name it on the create, so the whole history carries an identifier it held before the request went out. The webapp makes this model visible. An intent detail page shows the latest action and the action chain. The related order shows the broker's execution state. Your software can read the same underlying records through the API. This separation also makes operational questions more precise. `SENT` describes dispatch. `FILLED` describes a broker outcome. An instruction can be sent successfully and still be rejected by the broker. See [SENT is not FILLED](/pages/blog/articles/sent-is-not-filled) for the distinction. ## Controls live outside the strategy A team should be able to change an account's trading permissions without changing every application that uses it. Anthid gives those constraints a shared enforcement point on the submission path. You can enable or disable trading at organization, account, and symbol scope. Account and symbol controls also support trading windows and maximum order size. The applicable controls combine restrictively: permission at one level does not override a refusal at another. For example, a strategy might request 500 shares while the account's maximum order size is 100. Anthid refuses that submission before dispatch and returns the controls that triggered. The team can inspect the refusal separately from accepted intents and broker rejections. It can also evaluate a hypothetical order before submitting it. The webapp exposes account and symbol controls so authorized operators can manage them alongside the activity they govern. Control changes have their own attributed history, making a changed limit part of the operational record. The current boundaries are specific. Organization scope provides a trading enable/disable control. Risk and time controls live at account and symbol scope, with availability depending on the plan. Maximum daily loss and maximum position size can be stored today, but are not enforced by the pre-trade evaluation. A control change also takes time to propagate; a kill switch should not be read as an instantaneous cancellation of working broker orders. The [controls guide](/pages/controls/overview) describes the current behavior. ## People and applications work from the same platform The API and the webapp serve different parts of the same workflow. Your application submits intents and consumes events. In the webapp, your team can inspect activity across accounts, open intent and order details, follow live streams, manage broker connections, and review controls. Authorized administrators can also place and cancel orders through the order ticket. This is useful during an ordinary handoff. A strategy submits an order, another person investigates it, and an administrator changes what the account is allowed to do next. They can refer to the same intent, order, and account instead of reconciling several private versions of the activity. Organization membership and permissions determine who can act. The ability to view a control does not automatically grant permission to change it, and access to a dashboard does not itself grant permission to submit orders. Broker connectivity belongs to this shared platform too. Anthid stores broker credentials encrypted and manages the sessions behind connected accounts. Your applications use Anthid's contract while the broker adapters handle the broker-specific exchange. [Lightspeed Connect](/pages/brokers/overview) is available today, in beta. Its supported order types, destinations, and time-in-force values are documented explicitly. A common interface preserves your application's model while keeping those broker capabilities visible. ## The record stays useful after the trade A live stream tells you what is arriving now. The [ledger](/api-reference/ledger/overview) lets you query the activity later, including order and position history across the accounts your organization can access. The webapp builds operational analytics on that record. Order analytics show acknowledgement and fill timing, outcomes, and amendments. Exposure analytics show how recorded positions changed over time. Exposure is valued using the broker's average cost, so read it as that view of the book rather than a live market valuation. Those views help a team ask better questions. Did acknowledgements slow down during the session? Were orders repeatedly amended? Which orders were rejected? How did the recorded exposure change? You can investigate through the webapp or use the API to feed your own reporting and reconciliation workflow. The record also extends beyond successful execution. Accepted instructions, control changes, and qualifying submission refusals preserve different parts of the explanation. An account where no order reached the broker can still have meaningful activity to review. ## Build the trading logic on top Anthid's role is to provide the operational foundation around your trading decisions. Your team chooses what to trade and supplies the strategy. Your broker carries out the supported orders. Anthid connects the instruction, the controls, the broker interaction, and the record your team uses to operate the system. That is the value beyond an EMS/OMS label: a common platform for the applications sending instructions and the people responsible for them. To see the workflow end to end, start with a paper account. Submit an intent, inspect its action chain and broker outcome, follow the live updates, and query the resulting history. Then configure an order-size limit and inspect a refusal. The [quickstart](/pages/intro/quickstart) is the place to begin. # Build vs buy: the real cost of trading infrastructure Source: https://docs.anthid.com/pages/blog/articles/build-vs-buy-trading-infrastructure Why the estimate for an execution layer is always wrong, what the layer actually costs to staff, and how to settle the question in an afternoon Every team that automates a trading strategy eventually prices the execution layer: the code between the strategy and the broker that carries an order, checks it, tracks it, and records what happened. Building looks cheap because the inputs are already paid for: you have engineers, the broker has an API, and the first version seems like a few weeks of work. The math feels obvious. It is also usually wrong, because the estimate describes the broker client and the cost lives in the layer around it. ## What the build estimate always misses The first 80% of the work is a demo. Orders go out, fills come back, everyone is pleased. That 80% is the broker client. The last 20% is the layer, and it is the part that never ends. Rejected orders with undocumented causes, and no record that the instruction existed before the rejection. Websockets that drop mid session and take your fill events with them, and nothing durable to reconcile against. Positions that drift from what the broker says, discovered at the worst moment. A retry that double-sends because nothing made submission idempotent. A replace that silently loses the original quantity. Risk checks you keep meaning to write properly. Broker API changes that land on their schedule, not yours. None of this appears in the original estimate, because the original estimate described software and this is an operation. Software gets finished. Operations get staffed. ## Put rough numbers on it Run the back of the envelope math for your own team. A capable engineer costs somewhere north of \$200,000 a year fully loaded. A serious first version takes months, not weeks, once the order lifecycle, reconciliation, monitoring, and failure handling are real. After launch, maintaining the layer quietly eats a meaningful slice of someone's time forever, and doubles the day you add a second broker, because now there are two sets of formats, error codes, and quirks to keep aligned underneath it. Then add the cost that never shows up on a spreadsheet: every hour spent on the layer is an hour not spent on the strategy, and the strategy is the only part that makes money. For a small team, that opportunity cost dwarfs the salary math. ## What buying actually gets you An order and execution management layer as an API. Anthid is one example. You describe the outcome you want as an [intent](/pages/concepts/intents); Anthid records it before contacting the broker, in the same transaction that queues it, checks it against your pre-trade controls, routes it, and streams back what happened. Dispatch state and fill state are reported separately, so sent is never read as filled. Amendments are kept as sequenced history, and everything lands in a [durable ledger](/api-reference/ledger/overview) that is retained for six years. Trading windows and order size limits are enforced before an order leaves, and every refusal is recorded. The broker sits behind that layer. [Lightspeed](/pages/brokers/lightspeed-connect) is connected today, and the instruction is expressed in Anthid's terms rather than the broker's. > [!WARNING] > > Maximum daily loss and maximum position size can be configured today, but they are stored rather than enforced: the pre-trade evaluation checks order size alone. Keep those checks in your own application until [Controls Overview](/pages/controls/overview) says otherwise. You also inherit numbers a small team rarely reaches alone: 99.99% uptime and median order acknowledgement of 42ms, with someone else carrying the pager. > [!NOTE] > > Those numbers describe how the platform has been running, not a commitment. The connected broker integration is in beta, and a broker integration carries no service level commitment. See [Service levels](/pages/brokers/overview#service-levels). ## When building still wins Sometimes it does, and pretending otherwise would be silly. If you run a large firm with a dedicated platform team, colocated hardware, and latency requirements measured in microseconds, the execution layer is part of your edge and you should own it. The same goes if your strategy depends on broker behavior so unusual that no shared platform will model it. Those cases are real. They are also rare, and most teams citing them are three people with a Slack channel named #infra. ## How to decide in an afternoon You do not need a committee for this one. Take a strategy you already have, point it at a managed platform's paper trading tier, and see how far you get before dinner. Anthid's free tier needs no credit card and no sales call. If you ship a working paper strategy in an afternoon, you have your answer, because the build option cannot even schedule its kickoff meeting that fast. > **[Quickstart](/pages/intro/quickstart)** > > Create your first intent against a paper account. Controls apply to new `CREATE` instructions. They do not reevaluate replacements or cancel resting orders. HTTP create retries are not deduplicated; reconcile an uncertain submission before retrying. See [Intents](/api-reference/intents/overview) for the current contract. # Why use Anthid instead of building your own execution layer Source: https://docs.anthid.com/pages/blog/articles/overview What the layer between a strategy and a broker actually costs to own, what Anthid takes off your plate, and when building it yourself is still the right call Anthid is an order and execution management layer delivered as an API. Your systems submit an instruction. Anthid records it, checks it against the limits you set, routes it to your broker, streams back what happened, and keeps the whole record. Broker connectivity is one capability of that layer, not the layer itself. Every developer who automates a strategy eventually decides whether to build that layer or run on one. Building it feels productive at first. The broker's API docs look manageable, the first test order goes through, and it seems like you saved money. Then reality shows up. ## What rolling your own actually costs The broker client is the visible part of the work and the smallest. What sits around it is the layer, and the layer is a permanent obligation. Orders get rejected for reasons the docs never mention, and you need somewhere to record that the instruction existed before the rejection came back. A websocket drops at 2am and your fill events quietly vanish, so you need a record to reconcile against. Positions drift out of sync with the broker and you lose a morning working out which side is right. A retry double-sends an order because nothing made the submission idempotent. A risk check you meant to write properly is still a comment. Then you add a second broker and discover that everything above the client took the first client's shape: its order formats, its error codes, its idea of what a "filled" event even looks like. Here is the uncomfortable part. None of that work makes your strategy better. It keeps the lights on, and that is all it does. The hours you spend on the layer are hours you are not spending on the thing that actually makes money. ## What Anthid does instead Anthid runs that layer as a service, and it is built around one rule: the instruction is recorded before anyone is told it succeeded. You describe the outcome you want as an [intent](/pages/concepts/intents), in Anthid's own terms. The intent is written to an append-only log in the same transaction that queues it for the broker, so no order can reach a venue without a record. A retry under the same `Idempotency-Key` is answered with the intent the first request made rather than placing a second order; see [Request correlation and retries](/api-reference/intents/overview#request-correlation-and-retries). A replace or a cancel is appended as a new sequenced action rather than overwriting the original, so the full history of what was instructed is always reconstructible. And the platform reports two things separately: how far it got in handing the order to the broker, and what the broker then did with it. Sent is never mistaken for filled. [Controls](/pages/controls/overview) run before the order ever leaves the platform. Trading windows and maximum order size are enforced at submission, so an oversized order, or one sent outside approved hours, is stopped before it reaches the broker rather than discovered after the damage is done. Organization controls act as a kill switch across every connected account, which is the fastest way to stop trading during an incident, and every refusal is recorded with the same attribution as an accepted order. > [!WARNING] > > Maximum daily loss and maximum position size can be configured today, but they are stored rather than enforced: the pre-trade evaluation checks order size alone. Keep those checks in your own application until [Controls Overview](/pages/controls/overview) says otherwise. Every order, fill, and position update arrives in one normalized stream and lands in a durable [ledger](/api-reference/ledger/overview) that is kept for six years, so when you ask "what actually happened to that order," there is a real answer. The broker sits behind all of that. An intent is translated into each broker's dialect at routing time, so the instruction, the controls, the stream, and the record are the same whichever connected account is on the other end. [Lightspeed](/pages/brokers/lightspeed-connect) is connected today, in beta, [more are on the roadmap](/pages/community/roadmap), and adding one is a config change rather than a rewrite. ## The numbers you would otherwise own Anthid runs at 99.99% uptime with median order acknowledgement of 42ms. Those figures are easy to read and very hard to earn. If you build your own layer, you own them yourself, along with the monitoring, the failover, and the 3am pages when something slips. Most small teams never get close, and the gap shows up at the worst possible moment, which in trading means an open position you cannot see. > [!NOTE] > > Those numbers describe how the platform has been running, not a commitment. The connected broker integration is in beta, and a broker integration carries no service level commitment. See [Service levels](/pages/brokers/overview#service-levels) for what your plan commits to and when that changes. ## When building your own still makes sense To be fair, custom infrastructure has its place. If you are a large firm with a dedicated platform team, colocated servers, and latency requirements measured in microseconds, you probably should own the whole stack. For a solo developer, a small quant team, or a startup shipping its first strategies, the math points the other way. You are not really choosing between free and paid. You are choosing between paying with months of engineering time or paying for a service that already solved the problem. ## Try it before you commit Anthid has a free tier with paper trading, no credit card and no sales call required. Paper accounts run against the same API surface as live, so you can point your strategy at the API, run it against paper money, and see how it feels in an afternoon. Connecting a live broker account moves you to a [Starter or Professional plan](/api-reference/accounts/overview#availability), but nothing about your integration changes when it does. If the goal is to ship a live strategy this week instead of this quarter, that is the whole argument in one sentence. > **[Quickstart](/pages/intro/quickstart)** > > Create your first intent against a paper account. > **[Introduction](/pages/intro/introduction)** > > How the platform fits between your application and a connected trading account. ## More articles > **[Anthid is more than an EMS/OMS](/pages/blog/articles/beyond-ems-oms)** > > How broker connectivity, durable instructions, controls, live operations, and history fit together as one managed trading platform. > **[Recorded before acknowledged](/pages/blog/articles/recorded-before-acknowledged)** > > Why the record of an instruction exists before the broker hears about it, and what that lets you answer. > **[SENT is not FILLED](/pages/blog/articles/sent-is-not-filled)** > > Dispatch state and broker outcome are two fields. Which one answers which question. > **[Build vs buy](/pages/blog/articles/build-vs-buy-trading-infrastructure)** > > What the execution layer costs once the estimate meets the operation. > **[Anthid vs Lightspeed](/pages/blog/articles/anthid-vs-lightspeed)** > > Whether to build your own layer on Lightspeed Connect or run Anthid's with Lightspeed behind it. > **[Adding a second broker](/pages/blog/articles/add-a-second-broker-without-a-rewrite)** > > Why the second broker breaks a home-built layer, and why it is a setting on Anthid. # Recorded before acknowledged: why an instruction and its execution fail independently Source: https://docs.anthid.com/pages/blog/articles/recorded-before-acknowledged How Anthid orders the record and the execution of an instruction, what that ordering rules out, and what it lets you answer when nothing came back from the broker Anthid writes your instruction to a durable record before it tells you the instruction was accepted, and before it contacts a broker. That ordering is the rule the rest of the execution layer is built around. This article explains why it matters, what it rules out, and what it lets you answer on the day something goes wrong. ## An instruction and its outcome fail independently Most broker APIs give you one object. You send an order, and what comes back is both your request and the live state of that order at the venue. While that is convenient, it collapses two things that do not fail together. A broker can be unreachable. It can reject an order. It can acknowledge an order and then lose it. It can fill an order hours later. In every one of those cases your instruction was real, was authorized, and was acted on by the platform. A model where the record begins only once the broker accepts cannot describe the moment in between, and it cannot answer the question a review asks first: what was attempted when nothing came back? Anthid keeps the two apart. An [intent](/pages/concepts/intents) is what you asked for. An [order](/pages/concepts/orders) is what came of it at the broker. The intent exists first, and it exists whether or not an order ever does. ## What "recorded before acknowledged" means in practice When you submit an intent, Anthid commits it to an append-only event stream in the same database transaction that queues it for execution. You receive a response only after that transaction commits. ```bash theme={null} curl https://api.anthid.com/v1/intents \ -X POST \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "trading_account_id": "", "command": { "type": "CREATE", "payload": { "symbol": "AAPL", "order_side": "BUY", "spec": { "Limit": { "price": "100.00", "quantity": "10", "time_in_force": "DAY" } } } } }' ``` ```json theme={null} { "intent_id": "019c0538-86f2-7c32-9c1e-6f79cbfdb7b4" } ``` That response is a receipt for the record, not for the fill. It says three things: your controls allowed the instruction, the instruction is now durable, and the work to carry it to the broker is queued. It says nothing about execution yet, and it is not supposed to. See [SENT is not FILLED](/pages/blog/articles/sent-is-not-filled) for how execution is reported. Because the record and the queue entry are one transaction, there is no window in which an instruction is in flight but unrecorded. Either both exist or neither does. No order can reach a broker through Anthid without a record that says who asked for it. ## The record cannot be rewritten The instruction stream is append-only, and that is enforced by the database rather than by application code. A trigger rejects any attempt to update or delete a row. The application has no path to modify a recorded instruction, and neither does an ordinary database session. Corrections are not edits. A replace or a cancel is appended as a new, sequenced action against the same intent, carrying the credential that sent it. Amend an order twice and then pull it, and the intent holds four actions in order: the create, two replaces, and the cancel. Read them back with one call. ```bash theme={null} curl "https://api.anthid.com/v1/intents//actions" \ -H "x-api-key: " ``` > [!NOTE] > > A `REPLACE` carries a complete order specification, not a patch. Fields left out of the replacement are not inherited from the original. The record keeps every version, so the history shows exactly what each amendment asked for, but a replace that names only a new price will not preserve the original quantity. See the [Intents API](/api-reference/intents/overview). ## What each record carries A record is only useful in a dispute if it can say who, when, and from where. Every action carries: | Field | What it establishes | | - | - | | Resolved order payload | The instruction exactly as Anthid understood it | | Action type and sequence | Create, replace, or cancel, and its position in the intent's history | | Credential | Which of your credentials sent it, and of what kind: an API key, a signed-in user, or an Anthid service acting for you | | Intent identifier | The intent the action belongs to, carried unchanged through every later action; yours if you named it on the create | | Request identifier | Ties the record to the single request that produced it | | Arrival and write timestamps | When the request reached the edge, recorded separately from when the row was committed | | Client address | The calling address as observed at the network edge, never from a caller-supplied header | Attribution is taken from the request every time and never inherited from the thing being changed. One credential cancelling what another credential placed is visible rather than hidden. That is the half of a disputed order that turns out to matter, and it cannot be reconstructed afterwards if it was not written at the time. ## Refusals are recorded too A record of what Anthid accepted is only half of what a supervisory review asks for. The other half is what it stopped. Submissions refused by your [controls](/pages/controls/overview), by an entitlement, or by authorization are written to their own append-only stream with the same attribution and the submission exactly as it arrived. A controls refusal carries the specific limits that triggered, which is the same detail the caller received. The record is written independently of the transaction the order would have used, so a refusal cannot be lost to the same rollback that discarded the order. ```bash theme={null} curl "https://api.anthid.com/v1/intents/rejections?stage=CONTROLS&days=7" \ -H "x-api-key: " ``` One case is worth stating plainly: an attempt to submit against an account your organization does not own is refused and recorded, with the credential behind it. The caller is told the account was not found. The record says what was tried. ## What this lets you answer Put the pieces together and the awkward questions get short answers. *The broker never responded.* The intent exists, its actions are recorded, and its dispatch state says how far the platform got. Nothing about the instruction depends on the broker having spoken. *Two systems trade the same account and one of them placed something it should not have.* The credential on the action says which one. An intent id that system minted itself says which run. *An order was amended and nobody remembers what it originally said.* The action chain holds every version in sequence. *A limit blocked an order last Tuesday and someone wants to know why.* The refusal record names the controls that fired, and the control change stream says who set them and when. *A regulator asks two years from now.* The instruction stream is retained for six years, with the attribution intact. See [Compliance](/pages/about/compliance) for the full retention model. ## Why this makes the broker a setting The same separation is what keeps the layer broker-agnostic. An intent is expressed in Anthid's own terms and recorded that way. It is translated into a broker's dialect only at routing time, which is after the record exists. So the instruction reads the same whichever broker it goes to, [Lightspeed](/pages/brokers/lightspeed-connect) today, and the record of it does not change when the account behind it does. > **[Intents](/pages/concepts/intents)** > > The model in detail: intents, actions, dispatch, and outcome. > **[Compliance](/pages/about/compliance)** > > What is recorded, how it is protected, and how long it is kept. # SENT is not FILLED: the two-state order lifecycle Source: https://docs.anthid.com/pages/blog/articles/sent-is-not-filled Why Anthid reports how far the platform got and what the broker did as two separate fields, what goes wrong when they are collapsed into one, and which field answers which question The single most common bug written against Anthid is a dashboard or a strategy that treats `SENT` as done. It is an easy mistake, because on most broker APIs a status is a status. On Anthid an intent reports two different answers about where things stand, and they are not interchangeable. This article explains the two, why there are two, and which one to read for each question you actually have. ## Two questions, two fields An instruction goes through two independent things. Anthid has to carry it to the broker, and then the broker has to do something with it. Each is reported on the intent. **Dispatch** is how far Anthid got in handing the instruction over. It is the `state` field, and it moves through four values. | `state` | What it means | | - | - | | `PENDING` | Accepted and recorded, waiting to be picked up | | `PROCESSING` | Being acted on | | `SENT` | Handed to the broker, and the broker accepted the handoff | | `FAILED` | Could not be handed to the broker | Dispatch is terminal at `SENT` or `FAILED`. Once an intent is `SENT`, it stays `SENT` forever, including after the order fills, is cancelled, or is rejected at the venue. That is the point at which people go wrong: `SENT` is the last thing dispatch will ever say, so it looks final, and it is final, but only about the handoff. **Outcome** is what the broker did with the order that followed. It is the `runtime_status` field, and it keeps moving after dispatch has stopped. | `runtime_status` | What it means | | - | - | | `null` | The broker has not said anything yet | | `PENDING` | The broker holds the order and has not worked it | | `OPEN` | Working at the venue | | `PARTIALLY_FILLED` | Some quantity has traded, the rest is still working | | `FILLED` | Fully executed | | `CANCELLED` | Withdrawn | | `REJECTED` | Refused at the venue | A perfectly healthy order reads `state: SENT, runtime_status: OPEN` for as long as it rests, then `state: SENT, runtime_status: FILLED`. A rejected one reads `state: SENT, runtime_status: REJECTED`. In both cases dispatch succeeded. The handoff and the outcome are different facts. ## Why one field cannot do this job Collapse the two into one status and something has to give. If the single status is dispatch-shaped, it ends at `SENT` and can never say filled. If it is outcome-shaped, it cannot begin until the broker speaks, so an order the broker never acknowledged has no status at all, which is exactly the order you most need to point at. And if it tries to be both, `PENDING` becomes ambiguous: is the platform still holding the instruction, or is the broker? That last ambiguity is why `runtime_status` is `null` rather than `PENDING` before the broker reports. A null says Anthid has heard nothing. `PENDING` is the broker saying it has the order and has not worked it. A caller that only wants one value can treat null as pending. A caller watching for a stuck order needs to tell them apart, because an intent that has been `SENT` for a minute with a null outcome is a different problem from one the broker is holding. > [!WARNING] > > **`SENT` does not mean filled.** A report that counts `SENT` intents as executed overstates what traded, by every order that was rejected, cancelled, or is still resting. Read `runtime_status`, or read `filled_quantity` from the resulting order. ## The order carries the same idea twice more Once the broker acknowledges an order there is an [order record](/pages/concepts/orders) alongside the intent, and it makes the same distinction in two more places. The order carries `order_status`, the precise lifecycle position in the venue's own vocabulary, and `order_state`, a coarse rollup computed from it for filtering lists and driving dashboards. Because the state is derived from the status, the two cannot disagree. | `order_state` | Statuses that map to it | | - | - | | `Open` | `PENDING_NEW`, `NEW`, `PARTIALLY_FILLED`, `PENDING_CANCEL`, `PENDING_REPLACE`, `REPLACED` | | `Closed` | `FILLED`, `CANCELLED`, `STOPPED` | | `Rejected` | `REJECTED` | Two consequences bite in reporting, and both are the `SENT` mistake in a different coat. A partially filled order is `Open`, not `Closed`. It has traded, but it is still working, and closing it out in your own model on the first fill strands the remainder. `Closed` is not a synonym for filled. A cancelled order and a fully filled order are both `Closed`, because both are finished. Which of the two it was comes from `order_status`, or from comparing `filled_quantity` against `quantity`. > [!NOTE] > > The two fields do not share a casing. `order_state` serializes as `Open`, `Closed`, `Rejected`. `order_status` beside it serializes as `NEW`, `FILLED`, `PARTIALLY_FILLED`. A comparison against `"OPEN"` never matches, and it fails silently. ## Which field answers which question | You want to know | Read | Where | | - | - | - | | Did Anthid accept and record the instruction | The `intent_id` in the submit response | `POST /v1/intents` | | Did Anthid hand it to the broker | `state` | `GET /v1/intents/{intent_id}` | | Is the platform still holding it, or has the broker not spoken | `state` is `PENDING` or `PROCESSING`, versus `SENT` with `runtime_status` null | `GET /v1/intents/{intent_id}` | | What did the broker do with it | `runtime_status` | `GET /v1/intents/{intent_id}` | | Is the order still working | `order_state` is `Open` | `GET /v1/orders?environment=&intent_id=...` | | How much actually traded | `filled_quantity` | `GET /v1/orders?environment=&intent_id=...` | | What it traded at | `average_price` | Same | `filled_quantity` is the only field that reports how much executed, and it is meaningful at every state: partial on an open order, complete on a filled one, and zero on a cancelled one that never traded. Do not infer execution from any state or status field. ## Reading it from the stream The same facts arrive live over gRPC. A `BrokerOrder` event on the [streaming API](/api-reference/stream/overview) carries the order's status and its cumulative `filled_quantity`, and a `BrokerTrade` event carries each individual execution for the venues that report them. If you want to know how much of an order has traded, read `filled_quantity` on `BrokerOrder`; it already aggregates the trades. Reconnecting does not replay every missed transition. Orders default to live delivery, but `ORDER_DELIVERY_PRIMED` first sends the latest retained snapshot per order and a completion marker. See [Streaming](/api-reference/stream/overview#initial-state-and-live-delivery). After a disconnect, read `GET /v1/orders?environment=` for the latest known state per order rather than assuming continuity. The stream is a view over the record, not a replacement for it. ## A worked example An intent is submitted at 09:30:00 and returns an `intent_id`. Reading it back: ```json theme={null} { "state": "PENDING", "runtime_status": null } ``` Anthid has recorded it and not yet picked it up. A moment later: ```json theme={null} { "state": "SENT", "runtime_status": null } ``` The broker accepted the handoff and has not reported. This is normal for a few hundred milliseconds. If it lasts a minute, something is wrong on the broker side, and this is the shape a stuck-order monitor should look for. Then: ```json theme={null} { "state": "SENT", "runtime_status": "OPEN" } ``` Working at the venue. Then: ```json theme={null} { "state": "SENT", "runtime_status": "PARTIALLY_FILLED" } ``` Some traded. The order is `Open`, and `filled_quantity` on the order record says how much. Finally: ```json theme={null} { "state": "SENT", "runtime_status": "FILLED" } ``` Dispatch never changed after the second reading. Everything you cared about happened in the other field. > **[Intents](/pages/concepts/intents)** > > The concept page: dispatch, outcome, and the action history. > **[Intents API](/api-reference/intents/overview)** > > Field names, endpoints, and the `state` versus `runtime_status` contract. # Blog Source: https://docs.anthid.com/pages/blog/overview Platform updates, engineering notes, and practical guidance from the Anthid team The Anthid blog covers platform updates, engineering decisions, and guidance for teams that need an execution and risk layer between their strategies and their brokers. The rest of the documentation describes how the platform works today and is kept current as Anthid changes. The blog is where we write about the reasoning behind that work, what shipped recently, and how to apply it. It comes in three parts: platform news for what changed, articles for the pieces meant to stay useful, and dated posts for everything else. ## Platform news What shipped, what changed, and whether it affects your integration. > **[Platform update | August 2026](/pages/blog/platform-news/august-2026)** > > **August 20, 2026.** Lower latency under concurrent load, sessions that recover from broker-side drops on their own, and a smaller metrics surface. ## Articles Longer pieces on how the platform is built and how to build on it. They are kept as a reference rather than filed by date. > **[Anthid is more than an EMS/OMS](/pages/blog/articles/beyond-ems-oms)** > > How broker connectivity, durable instructions, controls, live operations, and history fit together as one managed trading platform. > **[Why use Anthid instead of building your own execution layer](/pages/blog/articles/overview)** > > What the layer between a strategy and a broker actually costs to own, what Anthid takes off your plate, and when building it yourself is still the right call. > **[Recorded before acknowledged](/pages/blog/articles/recorded-before-acknowledged)** > > How Anthid orders the record and the execution of an instruction, and what that lets you answer when nothing came back from the broker. > **[SENT is not FILLED](/pages/blog/articles/sent-is-not-filled)** > > Why dispatch state and broker outcome are two fields, and which one answers which question. ## Latest posts > **[Welcome to the Anthid blog](/pages/blog/welcome)** > > **August 5, 2026.** What we plan to publish here and how it fits alongside the rest of the documentation. ## Elsewhere > **[Roadmap](/pages/community/roadmap)** > > What we are building next. > **[Community](/pages/community/overview)** > > Ask questions and share feedback with the team. # Platform update | August 2026 Source: https://docs.anthid.com/pages/blog/platform-news/august-2026 Lower latency under concurrent load, sessions that recover from broker-side drops on their own, and a smaller metrics surface Here is what changed on Anthid over the past few weeks. Most of the time went into latency and session handling. ## Latency We had been telling ourselves the routing overhead was fine. Then we sat down and profiled it, and it was not. The hot path was doing work on every order that did not need to be there at all. Taking that work out helped. The larger win came from changing how sessions are handed off when several accounts are connected at once. p99 came down 39.28%. More useful than that headline number: the tail got much flatter under concurrent load. Behavior is more predictable when things are busy, rather than only faster on a quiet day. > **[See the current numbers](/api-reference/ledger/overview)** > > `GET /v1/platform/latency` returns platform intent to broker handoff percentiles. Read `window_seconds` for the span they cover: it is the trailing hour when the platform is busy, and reaches further back when it is quiet. ## Session recovery Reconnects after a broker-side drop are fixed. Recovery used to wait on someone noticing and stepping in. The session now comes back on its own, which is how it should have worked from the beginning. ## Multiple accounts Running multiple accounts is smoother. Adding a second or third account used to be a path that got less testing than the single account case, and it showed. That is evened out now. ## Metrics We had been collecting far more than anyone was reading. We cut it back to the things people check: fill latency, reject rates, and session health. Everything loads quicker, and a problem is easier to spot when there is one. ## Dashboard Order flow updates live rather than trailing a few seconds behind, so what you are looking at is what is happening. There are still parts of the interface we are not happy with, and those are next. ## Tell us what is slow If you are running on this and something is slow or irritating, say so. That is what ends up at the top of the list. > **[Community](/pages/community/overview)** > > Bring it to the community Slack. This is the fastest way to reach the team. > **[Roadmap](/pages/community/roadmap)** > > What we are building next, and why. # Welcome to the Anthid blog Source: https://docs.anthid.com/pages/blog/welcome What we plan to publish here and how it fits alongside the rest of the documentation We are starting a blog alongside the Anthid documentation. The docs explain how the platform works today: how to connect a broker account, submit intents, configure controls, stream updates, and query the ledger. They are written to stay accurate and are revised whenever the platform changes. The blog is for the things that do not fit that shape. ## What we plan to publish > **Platform updates** > > New broker integrations, endpoints, and capabilities as they ship, with enough context to know whether they matter to your integration. > **Engineering notes** > > How parts of the platform are built and the tradeoffs behind them, from credential handling to control evaluation and the streaming layer. > **Practical guidance** > > Patterns for building on top of intents, controls, and the ledger that are more opinionated than reference documentation should be. ## Where to start If you are new to Anthid, the documentation is still the right entry point. > **[Introduction](/pages/intro/introduction)** > > How the platform fits between your application and a connected trading account. > **[Quickstart](/pages/intro/quickstart)** > > Create your first intent and start working with Anthid. ## Tell us what to write about If there is something you want explained in more depth, the [community Slack](/pages/community/overview) is the fastest way to reach us. Requests for deeper coverage of controls, broker behavior, or the streaming API are all fair game. For account or integration issues, contact [support@anthid.com](mailto:support@anthid.com). # Lightspeed Connect Source: https://docs.anthid.com/pages/brokers/lightspeed-connect Connect Lightspeed Connect accounts through Anthid. Lightspeed Connect is a supported broker integration, providing access to both paper and live trading environments through a unified platform interface. Anthid manages the broker connection lifecycle, authentication, streaming connectivity, account routing, and platform integration layers so applications can interact with Lightspeed through the same APIs used across Anthid. ## Availability > [!NOTE] > > **Stability: Beta.** The functionality documented on this page is implemented and routed end to end in both paper and live environments. The integration is usable in production and is still being hardened, so broker-specific behavior can change ahead of general availability. > > This integration carries no service level commitment. See [Integration stability](/pages/brokers/overview#integration-stability) and [Service levels](/pages/brokers/overview#service-levels). Lightspeed Connect is currently available to all organizations. Support includes: * Paper trading accounts * Live trading accounts * Real-time order updates * Real-time position updates * Intent-based order submission * Risk and trading controls * Historical ledger storage ## Supported Environments | Environment | Supported | | - | - | | Paper | ✓ | | Live | ✓ | Both environments can be connected simultaneously within the same organization. ## Broker Limits Lightspeed Connect imposes limits that Anthid respects and enforces. | Limit | Value | | - | - | | Daily Requests | 300,000 | | Requests Per Second | Determined by your Anthid pricing tier | Anthid may apply additional platform-level protections and controls to maintain reliability and fair usage. ## Supported Functionality Lightspeed Connect supports: * Market orders * Limit orders * Stop orders * Stop-limit orders * Smart routing and selected direct exchange routes * `DAY` time in force through the current end-to-end intent integration For the current compatibility matrices and exact intent values, see **Supported Order Types**, **Supported Destinations**, and **Supported Time In Force**. ## Trading Accounts Each Lightspeed account connected to Anthid is represented as a Trading Account. Trading Accounts can be used with: * Intents * Orders * Positions * Controls * Streaming API * Historical ledger APIs Applications interact with Trading Accounts rather than directly managing broker sessions. ## Authentication Lightspeed credentials are managed through Anthid Trading Accounts. Applications never communicate directly with Lightspeed APIs and do not need to manage broker authentication flows, session handling, or connection recovery logic. ## Reliability Anthid maintains broker connectivity on behalf of connected applications. This includes: * Connection management * Automatic reconnection * Event streaming * Order state synchronization * Position synchronization * Broker event processing Applications consume a consistent platform interface regardless of broker-specific implementation details. ## Related Resources * [Trading Accounts](/api-reference/accounts/overview) * [Intents](/pages/concepts/intents) * [Orders](/pages/concepts/orders) * [Positions](/pages/concepts/positions) * [Streaming API](/api-reference/stream/overview) * [Supported Order Types](/pages/brokers/supported-order-types) ## Support For questions about broker connectivity, request formats, or platform behavior, contact [support@anthid.com](mailto:support@anthid.com). # Brokers Source: https://docs.anthid.com/pages/brokers/overview Connect trading accounts through Anthid using a unified broker integration layer. Anthid connects to brokers on your behalf, so your systems deal with one interface instead of one per broker. Each broker has its own way of expressing an order, its own error vocabulary, its own connection behaviour, and its own rules about what it will accept. Connecting directly means learning and then permanently maintaining all of that, once per broker. Anthid absorbs it. Once a broker account is connected, it works the same way as every other: the same order submission, the same controls, the same live updates, and the same historical record. ## Supported Brokers > **[Lightspeed Connect](/pages/brokers/lightspeed-connect)** > > **Beta.** Available today for paper and live trading environments. Additional broker integrations will be added over time based on platform priorities and customer demand. ## Integration stability Each broker integration carries a stability level that describes how far along its production readiness is. The level applies to the broker integration itself. The platform APIs in front of it (intents, controls, streaming, ledger) are versioned and documented separately. | Broker | Stability | Paper | Live | | - | - | - | - | | Lightspeed Connect | Beta | ✓ | ✓ | ### Stability levels | Level | What it means | | - | - | | Alpha | Under active development. Behavior and capability coverage change without notice, and the integration is not intended for live trading. | | Beta | The documented capability surface is implemented and exercised end to end against both paper and live environments. The integration is usable in production, and is still being hardened as broker-specific edge cases are found. | | General availability (1.0) | Hardened through sustained production use. Broker-specific behavior is stable and changes are communicated ahead of time. | ### Service levels Broker integrations carry no service level commitment, at any stability level. No uptime, latency, throughput, or recovery target is committed for a broker integration, and its behavior is not covered by production support terms. Stability describes how hardened an integration is and how far ahead its changes are communicated — see [Integration stability](#integration-stability). It is not a level of service commitment, and reaching general availability does not add one. This applies to the broker integration layer specifically. Platform services in front of it (intents, controls, streaming, and ledger) are governed by their own terms. ### What beta means in practice The current integration is in beta. When building against it: * Everything documented on a broker page is implemented and routed end to end. Gaps are called out explicitly rather than left silent. See the capability matrices in **Supported Order Types**, **Supported Destinations**, and **Supported Time In Force**. * Broker-side behavior at the edges is still being hardened and may change as cases are found and fixed. This covers routing rejections, replace and cancel semantics, time-in-force handling, and reconnection during a session. * Capability coverage grows during beta. New order types, routes, and time-in-force values are added to a broker integration as they are validated. * Validate a strategy against a paper trading account before connecting a live one, and configure risk controls before the first live intent. * No service level is committed for a beta integration, or for any integration. See [Service levels](#service-levels). * Report unexpected broker behavior to [support@anthid.com](mailto:support@anthid.com). Beta feedback directly shapes what is hardened next. ## Unified Platform Layer All supported brokers integrate with the same Anthid platform services. This allows applications to use a consistent interface for: * Trading account management * Intent submission * Risk and trading controls * Order and position tracking * Real-time event streaming * Historical ledger queries Broker-specific capabilities and limitations remain documented on each broker's individual page. ## Broker Capabilities Supported functionality varies between brokers, and Anthid does not paper over the differences. An order using something the target broker does not support is refused rather than quietly altered. | Capability | Lightspeed Connect | | - | - | | Paper trading | ✓ | | Live trading | ✓ | | Order submission, amendment, cancellation | ✓ | | Live order updates | ✓ | | Live position updates | ✓ | | Direct exchange routing | ✓ | | Time in force beyond `DAY` | ✗ | Other differences include available order types, market access, rate limits, and extended-hours support. The full matrices are in [Supported Order Types](/pages/brokers/supported-order-types), [Supported Destinations](/pages/brokers/supported-destinations), and [Supported Time In Force](/pages/brokers/supported-time-in-force). ## Environments Where supported by the underlying broker, Anthid supports both paper and live trading environments. Applications can connect multiple trading accounts and operate across different environments simultaneously through the same platform APIs. ## Rate Limits Anthid applies platform-level rate limits while respecting the capabilities of each broker integration. Effective throughput may vary depending on broker-specific restrictions, exchange requirements, and account-level limitations. ## Why this is worth outsourcing Getting an order to a broker is the easy part of a broker integration. What follows is the expensive part, and it does not end: keeping credentials safe, keeping the connection alive and recovering it when it drops, translating between your model and the broker's, noticing when the two have drifted apart, and being awake when any of that fails during a trading session. None of that work differentiates your firm. It is a running cost that has to be staffed rather than a project that gets finished, and it doubles the day you add a second broker. Anthid runs it as managed infrastructure, so the question of which broker to trade through becomes a commercial decision rather than an engineering project. # Supported Destinations Source: https://docs.anthid.com/pages/brokers/supported-destinations Route strategy values accepted by Anthid and their broker support. ## Overview Exchange destinations determine where an order is routed for execution. `order_venue` names where a create intent is sent. Use the Anthid value in your intent request; Anthid translates it to the broker-specific destination. `SMART` delegates venue selection to the broker. Lightspeed Connect also supports the direct routes listed below. ## Route support | Intent value | Lightspeed destination | Lightspeed | Description | | - | - | - | - | | `SMART` | `SMART` | ✓ | Broker-managed smart routing | | `AMEX` | `AMEX` | ✓ | American Stock Exchange | | `ARCA` | `NYSEARCA` | ✓ | NYSE Arca | | `EDGA` | `EDGAROUT` | ✓ | EDGA Exchange | | `EDGX` | `EDGXROUT` | ✓ | EDGX Exchange | | `NSDQ` | `NSDQ` | ✓ | Nasdaq Stock Market | | `NYSE` | `NYSE` | ✓ | New York Stock Exchange | | `BATS` | None | ✗ | Reserved by the API enum; not currently routed by a supported integration | | `BATY` | None | ✗ | Reserved by the API enum; not currently routed by a supported integration | ## Request example ```json theme={null} { "command": { "type": "CREATE", "payload": { "symbol": "AAPL", "order_venue": "ARCA", "order_side": "BUY", "spec": { "Limit": { "price": "195.00", "quantity": "10", "time_in_force": "DAY" } } } } } ``` ## Notes * Direct routing sends an order to a specific exchange destination. * Smart routing delegates venue selection to the broker. * Destination availability can also depend on the instrument, account, environment, and market session. * A broker may reject a route after Anthid accepts the intent. Monitor the intent and order event lifecycle. # Supported Order Types Source: https://docs.anthid.com/pages/brokers/supported-order-types Order type support varies by broker. ## Overview Order types determine how an order is executed in the market. Different order types provide different trade-offs between execution certainty and price control. Before submitting an intent, verify that the target broker supports the requested order type. Requests using unsupported combinations of order type, destination, or time-in-force will be rejected. ## Intent API support The current intent request schema accepts four order specifications: | Order specification | JSON key | Lightspeed Connect | | - | - | - | | Market | `Market` | ✓ | | Limit | `Limit` | ✓ | | Stop market | `Stop` | ✓ | | Stop limit | `StopLimit` | ✓ | | Trailing stop | `TrailingStop` | Coming soon | This table describes order types that can be submitted through `POST /v1/intents`. Broker event and ledger models may contain additional order types observed from broker-side activity. > [!NOTE] > > **Trailing stop is not yet accepted.** `TrailingStop` is absent from the intent request schema, so an intent naming it is refused rather than routed. This is a platform limit rather than a broker one. > > Trailing stops placed directly with a broker are still reported: they appear in broker events and the ledger like any other order. Only submission through the intent API is pending. ## Order Type Definitions | Order type | Required fields | Description | | - | - | - | | `Market` | `quantity` | Executes at the best available market price. Execution is prioritized over price. | | `Limit` | `price`, `quantity`, `time_in_force` | Executes only at the specified limit price or better. | | `Stop` | `stop_price`, `quantity`, `time_in_force` | Becomes a market order after the stop price is reached. | | `StopLimit` | `price`, `stop_price`, `quantity`, `time_in_force` | Becomes a limit order after the stop price is reached. | | `TrailingStop` | Not applicable | Stop price that follows the market by a fixed offset. Not yet accepted; see the note above. | ## Examples Each order specification is nested below `command.payload.spec`. Numeric quantities and prices are strings. ```json theme={null} { "spec": { "StopLimit": { "price": "189.00", "stop_price": "190.00", "quantity": "100", "time_in_force": "DAY" } } } ``` ## Notes * Valid order-type, route, and time-in-force combinations remain broker-dependent. * Additional exchange, routing, environment, and account restrictions may apply. * A request can be accepted as an intent and later rejected by the broker. Monitor intent actions and order events for the final outcome. # Supported Time In Force Source: https://docs.anthid.com/pages/brokers/supported-time-in-force Available time-in-force instructions and broker support. ## Overview Time in Force (TIF) determines how long an order remains active before it is executed or canceled. Not all brokers support the same time-in-force instructions. Before submitting an order, verify that the selected broker supports the requested configuration. ## Broker Support Use the exact intent values shown in the first column. | Intent value | Common name | Lightspeed Connect | | - | - | - | | `DAY` | Day | ✓ | | `GOOD_TILL_CANCEL` | Good till canceled (GTC) | ✗ | | `IMMEDIATE_OR_CANCEL` | Immediate or cancel (IOC) | ✗ | | `AT_OPEN` | At the open (OPG) | ✗ | | `AT_CLOSE` | At the close (CLS) | ✗ | | `FILL_OR_KILL` | Fill or kill (FOK) | ✗ | | `EXT` | Extended hours | ✗ | The API enum includes every value in this table. The broker column indicates the values currently honored by the end-to-end integration. Market order requests do not include a `time_in_force`; Anthid defaults them to `DAY` during execution. ## Time In Force Definitions | Time in force | Description | | - | - | | `DAY` | Remains active for the current trading session. The broker cancels any remainder at the end of the session. | | `GOOD_TILL_CANCEL` | Remains active until filled, canceled, or expired under broker rules. | | `IMMEDIATE_OR_CANCEL` | Fills any immediately available quantity and cancels the remainder. | | `AT_OPEN` | Participates only in the market opening auction. | | `AT_CLOSE` | Participates only in the market closing auction. | | `FILL_OR_KILL` | Must fill the entire quantity immediately or be canceled. | | `EXT` | Requests eligibility for supported pre-market and after-hours sessions. | ## Examples ### DAY Buy 100 shares of AAPL at \$200. * 60 shares fill during the trading session. * 40 shares remain unfilled at market close. * The remaining 40 shares are automatically canceled. ### GOOD\_TILL\_CANCEL Buy 100 shares of AAPL at \$190. * The order remains active across multiple trading days. * The order stays active until filled, canceled, or expired by the broker. ### AT\_OPEN Buy 100 shares of AAPL with `AT_OPEN`. * The order participates only in the market opening auction. * If it cannot execute during the opening cross, it is canceled. ### IMMEDIATE\_OR\_CANCEL Buy 1,000 shares of AAPL with `IMMEDIATE_OR_CANCEL`. * 600 shares are immediately available. * 600 shares execute. * The remaining 400 shares are canceled immediately. ### AT\_CLOSE Sell 100 shares of AAPL with `AT_CLOSE`. * The order participates only in the closing auction. * If it cannot execute in the closing cross, it is canceled. ### FILL\_OR\_KILL Buy 1,000 shares of AAPL with `FILL_OR_KILL`. * The broker must fill all 1,000 shares immediately. * If the complete quantity is unavailable, the entire order is canceled. > [!NOTE] > > `SMART` is a route strategy, not a time-in-force value. See Supported Destinations for route values. # Community Source: https://docs.anthid.com/pages/community/overview Connect with other Anthid users and the Anthid team. Anthid provides community resources where users can ask questions, share feedback, discuss integrations, and connect with other developers building on the platform. Community channels are optional and should not be considered a replacement for official support. ## Slack The Anthid Slack workspace provides a place to: * Ask implementation questions * Discuss integrations and architecture * Share feedback and feature requests * Learn from other teams using Anthid * Stay informed about platform updates [Join the Anthid Community Slack](https://join.slack.com/t/anthidcommunity/shared_invite/zt-3z71r8kcy-qJgXv7aEsCj3QRaPLVedlA) ## Community Guidelines Please keep discussions: * Respectful * Professional * Relevant to Anthid and trading infrastructure * Free of sensitive credentials or account information Do not share: * API keys * Broker credentials * Private account information * Proprietary trading strategies ## Official Support Community channels are best suited for general questions and discussions. For account issues, billing questions, security concerns, or production-impacting problems, contact the Anthid team directly. * Support: [support@anthid.com](mailto:support@anthid.com) * Sales: [sales@anthid.com](mailto:sales@anthid.com) ## Feedback We actively use community feedback to prioritize: * New broker integrations * Platform capabilities * SDK improvements * Documentation improvements * Developer experience enhancements Feature requests and feedback are always welcome. # Roadmap Source: https://docs.anthid.com/pages/community/roadmap Where Anthid is headed next Anthid is focused on making broker-connected trading infrastructure faster to adopt, easier to operate, and more powerful over time. The roadmap is centered on three priorities: broader broker coverage, deeper trade risk controls, and better insight into strategy performance. ## 1. Additional brokers First and foremost, Anthid is expanding support for additional brokers. Every new broker integration increases the value of the platform layer: your team can connect more account providers without rebuilding broker clients, streaming adapters, routing logic, ledger storage, and controls for each one. ## 2. More trade risk controls Anthid will continue adding trade risk controls that help teams define how trading should operate across organizations, accounts, symbols, and time windows. The goal is to make the control layer more expressive while keeping it practical to manage, so teams can enforce trading policies without maintaining a custom risk platform. ## 3. Metrics, analysis, and insights Anthid is also expanding metrics, performance analysis, and additional insights on top of the ledger. Order counts, lifecycle latency percentiles, order health, daily realized returns, and position exposure are available today through the ledger and dashboard. Future work builds on those capabilities. See the [OMS walkthrough](/pages/guides/oms-workflow) and [Ledger analytics](/api-reference/ledger/overview#analytics-endpoints). ## 4. Strategy tagging Strategy tagging is planned to help teams label trades and activity by strategy, workflow, or internal system. With tagging, teams will be able to compare and contrast strategies more easily, analyze performance by segment, and turn the Anthid ledger into a clearer source of operational and trading intelligence. ## Community-driven development Anthid's development, prioritization, and future features will be driven with the community. Feedback from teams building on Anthid will help shape which broker integrations, controls, metrics, and workflow capabilities come next. # Intents Source: https://docs.anthid.com/pages/concepts/intents What an intent is, why Anthid records the instruction separately from the order, and what that separation buys you An intent is a request for Anthid to do something on a connected account: place an order, amend one, or withdraw one. It is Anthid's record of what you asked for, kept separately from whatever the broker subsequently does about it. Most broker APIs collapse the two. You send an order, and the thing you get back is both the request and the live state of that order at the venue. Anthid keeps them apart, because they answer different questions and they do not live the same length of time. ## Why the separation exists An instruction and its outcome fail independently. A broker can be unreachable, reject an order, acknowledge it and then lose it, or fill it hours later. In every one of those cases your instruction was real, was authorized, and was acted on by the platform. A model where the record only begins once the broker accepts cannot describe the moment in between, and cannot answer what was attempted when nothing came back. Anthid records the intent before it contacts the broker, in the same operation that queues the work. The order record comes later, when there is something at the broker to record. See [Compliance](/pages/about/compliance) for the guarantees around that ordering. The separation is also what keeps the platform broker-agnostic. An intent is expressed in Anthid's own terms and translated into each broker's dialect at routing time. The intent reads the same whichever connected broker it goes to. ## What happens to an intent Two separate things happen to an instruction, and an intent reports both. **Dispatch** is how far Anthid got in handing the instruction to the broker. It moves through four states. | State | What it means | | - | - | | `PENDING` | Accepted and recorded, waiting to be picked up | | `PROCESSING` | Being acted on | | `SENT` | Handed to the broker | | `FAILED` | Could not be handed to the broker | Dispatch stops changing once it reaches `SENT` or `FAILED`. It stays `SENT` afterwards no matter what the broker goes on to do. **Outcome** is what the broker did with the order that followed. It is reported alongside the dispatch state, and it keeps moving after dispatch has stopped. | Outcome | What it means | | - | - | | Not yet reported | The broker has not said anything about it | | `PENDING` | The broker holds the order and has not worked it | | `OPEN` | Working at the venue | | `PARTIALLY_FILLED` | Some quantity has traded, the rest is still working | | `FILLED` | Fully executed | | `CANCELLED` | Withdrawn | | `REJECTED` | Refused at the venue | > [!WARNING] > > **`SENT` does not mean filled.** It means Anthid passed the order to the broker and the broker accepted the handoff. Whether it then filled, partially filled, rested, or was rejected at the venue is the outcome, not the dispatch state. > > This is the single most common misreading of the model, and it matters: a dashboard or report that treats `SENT` as "done" will overstate what actually traded. The distinction between "not yet reported" and `PENDING` is deliberate. The first says Anthid has heard nothing back; the second is the broker saying it has the order and has not worked it. Anything watching for a stuck order needs to tell them apart. For the exact quantity traded, read the resulting [order](/pages/concepts/orders). The outcome tells you where the order stands; filled quantity tells you how much of it happened. ## Amendments build a history, not a replacement An intent is opened by a create, and can carry later amendments and cancellations. | Command | What it does | | - | - | | `CREATE` | Opens a new intent and the order it asks for | | `REPLACE` | Amends the order that intent commands | | `CANCEL` | Withdraws the order that intent commands | Each command is added to the intent as a new, sequenced entry rather than overwriting the previous one. So the intent accumulates a history: what was instructed, in what order, and by whom. Nothing is lost when an order is amended twice and then pulled. > **An amendment restates the whole order** > > A replace carries a complete replacement order, not a partial edit. Anything it does not name is not inherited from the original, so an amendment that changes only the price will not preserve the original quantity. This is worth knowing when reviewing how a system was built to amend orders. ## Attributing an order to the system that sent it By default Anthid mints the identifier for an intent and returns it on the response, which makes it useless for identifying a request that is still in flight or one whose response was lost. For that, your systems can mint the intent's identifier themselves and name it on the create. Anthid uses it as the intent's own, so every amendment, cancellation, order update, and fill under that intent carries an identifier the sender already held before the request went out, including any that arrive before the response does. Where several of your systems trade the same account, it is what makes an order attributable to the one that placed it. A lost response is also what the `Idempotency-Key` header is for: a retry under the same key is answered with the intent the first request made, rather than placing a second order. See [Request correlation and retries](/api-reference/intents/overview#request-correlation-and-retries). ## Where to see them Anthid serves intents from two places, each built for a different question. **Recent operational state** comes from the Intents API. Completed intents can remain there while archival and cleanup finish. Presence in this list does not mean an order is still actionable; read its outcome and resulting order state. **What happened** comes from the [ledger](/pages/concepts/orders). Every completed intent is kept there with its full instruction history, filterable and sortable, alongside the aggregate counts that reporting and reconciliation run on. When an intent reaches a terminal state, its action chain is archived asynchronously. Cleanup waits for the ledger to confirm the archive. The historical record is retained for six years with its attribution, while the plan controls the API query window. ## Related * [Orders](/pages/concepts/orders) for the record of what exists at the broker * [Positions](/pages/concepts/positions) for resulting holdings * [Controls](/pages/controls/overview) for what is evaluated before an intent is accepted * [Intents API reference](/api-reference/intents/overview) for endpoints, request shapes, and field names # Limits and entitlements Source: https://docs.anthid.com/pages/concepts/limits Check which features and usage limits your organization has, and distinguish query access from record retention. Your organization's billing claims describe its feature access and usage limits. Read the resolved claims instead of hardcoding values from a pricing example: plans and organization overrides can change them. ```bash theme={null} curl "https://api.anthid.com/v1/billing/organizations//claims" \ -H "x-api-key: " ``` Users and API keys can read their own organization. Service tokens require `billing:read`. A missing billing identity returns `404`; an unauthorized organization lookup returns `403`. ## Entitlements and permissions The response has `entitlements` and `limits`. Entitlements describe plan access, including tier, controls, trading, and support. Permissions separately determine what the credential may do. Having a live-trading entitlement does not grant intent edit permission. An API key's permissions are chosen when the key is created. Trading permissions are granted per environment, so a key can trade paper while only reading live: | Permission | Allows | | - | - | | `paper:intent:read`, `live:intent:read` | Reading intents, orders, positions, connection status, and streams for that environment's accounts | | `paper:intent:edit`, `live:intent:edit` | Creating, replacing, and cancelling orders on that environment's accounts | | `paper:control:read`, `live:control:read` | Reading and evaluating that environment's account and symbol controls | | `paper:control:edit`, `live:control:edit` | Changing that environment's account and symbol controls | | `organization:strategy:read`, `organization:strategy:edit` | Reading and changing strategy definitions | Listings of intents, rejections, orders and positions take a required `environment`, and are checked against it. Organization-wide controls need the permission in both environments. Streams name an environment when they subscribe and need the permission for that environment only, including organization-wide subscriptions. Paper and live use the same intent API, but live creates require live-trading access. Account controls are available on live plans; organization and symbol controls require premium controls access. See [Controls](/api-reference/controls/overview#availability) for the checks on each scope. ## Usage limits | Field in `limits` | What it governs | | - | - | | `requests_per_second` | Platform request rate allowance | | `retention_days` | Historical query window granted to the caller | | `broker_connections` | Broker account connection allowance | | `stream_connections` | Concurrent stream connection allowance | A platform request limit is separate from a broker's rate limit or supported order capabilities. Raising your Anthid allowance does not raise the broker's allowance. A rate-limited REST request can return `429`; pace requests and avoid blindly retrying order submissions. See [Request correlation and retries](/api-reference/intents/overview#request-correlation-and-retries). ## Retention is separate from query access Stream snapshots follow a separate policy: terminal orders and zero-quantity positions expire after one hour. Working orders and nonzero positions remain until updated. This affects priming and reconnects, not the ledger's history. See [Streaming delivery](/api-reference/stream/overview#initial-state-and-live-delivery). Order, position, and archived intent records have six-year storage retention. This does not grant every plan a six-year API query window. Ledger history requests are bounded by the caller's `retention_days`, and analytics also have endpoint-specific limits such as the 1 to 90 day window. See [Ledger](/api-reference/ledger/overview). # Orders Source: https://docs.anthid.com/pages/concepts/orders What an order record is, why it carries two identifiers, and why finished is not the same as filled An order is the record of something that exists, or existed, at a broker. Where an [intent](/pages/concepts/intents) is what you asked for, an order is what came of it. One intent owns the order it opened. Amendments and cancellations against that intent act on the same order rather than creating new ones, so an intent and its order stay paired for the intent's whole life. ## Two identifiers, two moments Every order carries an Anthid identifier and, once the broker has acknowledged it, the broker's own. Anthid's identifier exists as soon as there is an order record at all. The broker's exists only after the broker acknowledges the order, and an order the broker rejected outright may never get one. That is the point of having two. If Anthid used only the broker's identifier, an order sent but not yet acknowledged would be unaddressable, which is exactly the order you most want to be able to point at. Use Anthid's identifier to refer to an order within the platform, and the broker's when reconciling against a statement or a broker portal. Both are queryable. ## Finished is not the same as filled Each order carries two descriptions of where it stands, and they are not alternatives. The **status** is the precise lifecycle position, in the venue's own vocabulary. The **state** is a coarse rollup of that status, meant for filtering a list or driving a dashboard. The state is computed from the status rather than reported separately, so the two can never disagree. | State | What it covers | | - | - | | `Open` | Still working, including partially filled | | `Closed` | Finished, whether filled, cancelled, or stopped | | `Rejected` | Refused at the venue | The two fields do not share a casing, and it is worth noting before writing a comparison. The state reads `Open`, `Closed`, `Rejected`. The status beside it reads `NEW`, `FILLED`, `PARTIALLY_FILLED`. Testing a state against `"OPEN"` never matches. Two consequences are worth reading carefully, because both bite in reporting. A partially filled order is `Open`, not `Closed`. It has traded, but it is still working, and treating the first fill as completion strands the remainder. `Closed` is not a synonym for filled. A cancelled order and a fully filled order are both `Closed`, because both are finished. Which of the two it was comes from the status, or from comparing filled quantity against ordered quantity. > [!WARNING] > > **Do not infer execution from state.** `Closed` says the order is finished, not that it traded. Filled quantity is the only field that says how much actually executed, and it is meaningful at every state: partial on an open order, complete on a filled one, and zero on a cancelled one that never traded. ## What an order record holds Beyond identifiers and lifecycle, an order carries the terms it was working under and what has happened against it: * The symbol, side, and order type * Ordered quantity and the amount executed so far * Limit and stop prices, where the order type uses them * Average execution price across fills * Time in force, and where it was routed * The broker and trading account it belongs to * Your own reference, carried through from the intent * When Anthid observed this version of the order ## Current state and history Anthid keeps current state and history separately, and the distinction matters for correctness rather than only for speed. **Current** reads return the most recent known state of each order. This is what an account view or a dashboard should show. **History** reads return records across a time range. This is what reporting, reconciliation, and audit run on. Live updates are a view over the same activity, not a replacement for the record. Orders stream live by default. A subscription using `ORDER_DELIVERY_PRIMED` first receives the latest retained snapshot per order and a `primed` marker, then live changes. This rebuilds retained state, not every missed transition. Use the ledger for history and reconciliation. See [Streaming](/api-reference/stream/overview#initial-state-and-live-delivery). ## Related * [Intents](/pages/concepts/intents) for the instruction that opens an order * [Positions](/pages/concepts/positions) for what accumulated holdings look like * [Compliance](/pages/about/compliance) for what is recorded and how long it is kept * [Ledger API reference](/api-reference/ledger/overview) for endpoints, filters, field names, and pagination # Positions Source: https://docs.anthid.com/pages/concepts/positions What a position record represents, where the number comes from, and why Anthid reports the broker's figure rather than calculating its own A position is a holding in one symbol on one trading account: how much you hold, and what it cost on average. ## The broker is the source Position figures come from the broker. Anthid relays them rather than calculating them from the fills it has seen. This is a deliberate choice, and it is the most important thing to understand about the numbers on this page. Adding up observed fills sounds equivalent and is not. A calculated figure drifts from the broker's own whenever anything happens that Anthid did not observe: a fill that arrived while a connection was down, a corporate action, a trade placed directly with the broker, a broker-side adjustment or correction. Drift in a position number is worse than a gap, because it looks like data. Reporting what the broker says means the figure either matches your broker statement or is visibly stale, and both of those are recoverable. A silently wrong number is not. > **Positions can reflect activity Anthid never saw** > > Because the broker is the source, a position can include the result of trades that did not originate through Anthid. An account traded both through the platform and directly at the broker reports the combined holding, which is the true one. ## Knowing how fresh a figure is Each position record carries the time Anthid observed it. That is the age of the information, not the age of the holding. A figure that has not been updated in a while usually means nothing has changed about that symbol. After a connection problem it can also mean the platform has not caught up yet. Where the distinction matters, check the account's connection status: a healthy connection alone does not prove that a particular holding is current. Compare the observed timestamp and broker records when freshness matters. ## Current and historical As with orders, Anthid keeps the two apart. **Current** positions give the latest known holding per symbol. This is what an account view or dashboard should show. **Position history** gives records over a time range, for reporting and for analysing how a book changed. Live updates are a view over the same activity rather than a substitute for the record. Each subscription first receives the latest retained position per instrument, followed by a `primed` marker and live updates. These initial messages describe holdings, not new trades. They do not replay every change during a disconnect; use current positions and position history in the ledger to reconcile. ## A position is not an order The two answer different questions and can disagree without either being wrong. An order describes one instruction and its execution. A position describes the net result of everything that has executed. An open order contributes nothing to a position until it fills, and a filled order's contribution is permanent even though the order itself is finished. If you need to know why a position is what it is, read the orders behind it over the same window. The position record carries a holding, not an explanation of how it got there. ## Related * [Orders](/pages/concepts/orders) for the executions that build a position * [Intents](/pages/concepts/intents) for the instructions that open orders * [Ledger API reference](/api-reference/ledger/overview) for endpoints, filters, field names, and pagination # Controls Overview Source: https://docs.anthid.com/pages/controls/overview Protect your trading systems with organization, account, and symbol-level controls. ## Enforcement scope Controls are evaluated when the Intents API accepts a `CREATE` command. `REPLACE` and `CANCEL` do not run that evaluation. A kill switch blocks new creates in its scope; it does not cancel resting orders, flatten positions, or prevent a replacement. Manage existing orders separately. Only maximum order size and the configured manual switches and trading windows are enforced today. Daily-loss limits, maximum position size, and `stale_order_millis` are stored configuration, not active protection. The stale threshold does not reject or automatically cancel an aging order. ## Trading systems fail in predictable ways. A strategy deploys with an incorrect configuration. A market event creates unexpected volatility. A runaway algorithm continues submitting orders after crossing risk limits. An operator forgets to disable a strategy before maintenance. A symbol begins behaving differently than expected. Anthid Controls provide a centralized risk and governance layer that sits between your application and your broker. Controls are evaluated before execution, allowing you to enforce operational safeguards without modifying application logic. Controls can be applied at three levels: * **Organization.** Global controls that apply across your entire organization. * **Trading Account.** Controls specific to a broker account. * **Symbol.** Controls for individual symbols or instruments. The levels are additive. A narrower control tightens what a broader one already permits, and can never loosen it. See [How controls combine](#how-controls-combine). ## Availability Availability depends on the scope, not on controls as a whole. **Trading account controls are included with any plan that can trade live.** The account kill switch, account risk limits, and account trading windows are all available there, as is checking an order against them before you send it. Putting the account-level breaker on the top plan alone would have kept the guardrail away from exactly the trader most likely to need it. **Organization-wide and per-symbol controls require the Professional plan.** That covers the global kill switch and everything scoped to an individual instrument. Changing a control is restricted to organization administrators, or to an API credential your firm has explicitly granted control edit permission for that environment. Reading controls is available more broadly, so an operations dashboard can display the current state without being able to alter it. For the exact entitlement and permission model, and what a caller sees when it is not authorized, see the [Controls API reference](/api-reference/controls/overview#availability). ## Why Controls Matter Most trading platforms focus on order execution. Anthid focuses on both execution and protection. Controls allow engineering teams, operations teams, and trading teams to: * Prevent accidental trading activity. * Enforce risk limits consistently. * Restrict trading to approved market hours. * Protect against software defects and unexpected market conditions. * Reduce operational risk without modifying strategy code. * Apply changes immediately without redeploying applications. Controls become increasingly important as organizations scale from a single strategy to multiple systems, accounts, brokers, and operators. ## Organization Controls Organization controls provide the highest level of protection. These controls apply across every connected trading account and every strategy operating within the organization. ### Manual Controls Organization manual controls provide a global trading kill switch. When disabled, new create instructions are refused across the organization regardless of account-level or symbol-level configuration. Common use cases include: * Emergency trading suspension. * Scheduled maintenance windows. * Incident response. * Compliance-driven shutdowns. * Strategy rollout pauses. Use the organization switch to block new creates across accounts. Confirm propagation with an evaluation request and handle any working orders separately. ## Trading Account Controls Trading account controls provide account-specific governance and risk management. These controls allow organizations to manage individual broker accounts independently while maintaining organization-wide standards. ### Manual Controls Account manual controls enable or disable trading for a specific account. Examples include: * Disabling a paper account. * Pausing a live account during investigation. * Restricting a newly onboarded account. * Isolating a single account during an incident. ### Risk Controls Account risk controls limit exposure before orders reach the broker. Available controls include: * Maximum daily loss amount. * Maximum daily loss percentage. * Maximum position size. * Maximum order size. These controls help prevent excessive losses caused by software defects, unexpected market conditions, or operational mistakes. Rather than relying solely on strategy-level safeguards, account risk controls provide an independent enforcement layer. > [!WARNING] > > **Only maximum order size is enforced at submission today.** All four limits are accepted, stored, and returned by the API, but the pre-trade evaluation currently checks order size alone. Maximum daily loss amount, maximum daily loss percentage, and maximum position size do not yet block an order. > > Set them if you want the configuration in place ahead of enforcement, but do not treat them as active protection. Keep the corresponding checks in your own application until this note is removed. ### Time Controls Account time controls restrict when trading is permitted. Available controls include: * Trading start time. * Trading end time. * Stale order threshold. Examples include: * Restricting trading to regular market hours. * Preventing overnight activity. * Enforcing session-based trading windows. * Storing a stale-order threshold for future enforcement. > [!WARNING] > > **Trading windows are set in UTC.** The start and end times are compared against the current UTC time of day, not against the account's market timezone, and Anthid does not adjust them for daylight saving. > > Regular US market hours are `13:30:00` to `20:00:00` UTC during Eastern Daylight Time, and `14:30:00` to `21:00:00` UTC during Eastern Standard Time. A window meant to track market hours has to be updated when the offset changes. See [Time controls](/api-reference/controls/overview#time-controls) for the full detail. Time controls help ensure trading activity occurs only during approved operating periods. ## Symbol Controls Symbol controls provide the most granular level of protection. These controls allow organizations to manage individual symbols independently while inheriting broader organization and account protections. Symbol controls are particularly useful for volatile instruments, newly deployed strategies, and assets requiring additional oversight. ### Manual Controls Symbol manual controls allow specific symbols to be enabled or disabled. Examples include: * Disabling trading in a volatile stock. * Restricting a symbol during earnings announcements. * Temporarily suspending a problematic instrument. * Limiting exposure during market events. ### Risk Controls Symbol risk controls enforce limits on individual instruments. Available controls include: * Maximum daily loss amount. * Maximum daily loss percentage. * Maximum position size. * Maximum order size. The same enforcement limitation applies here as at the account level: only maximum order size is checked at submission today. This allows organizations to apply stricter controls to higher-risk symbols without affecting the rest of the account. For example, a leveraged ETF may require significantly tighter limits than a large-cap equity position. ### Time Controls Symbol time controls restrict trading windows for specific instruments. They are set in UTC, exactly as account time controls are. Examples include: * Restricting trading around earnings releases. * Limiting activity during specific market sessions. * Enforcing custom trading windows for individual symbols. * Blocking activity outside approved trading periods. Symbol-level time controls provide precise operational control without impacting other instruments. ## How controls combine Controls are additive, and the most restrictive one wins. Every level is evaluated independently against the same order. Organization, account, and symbol controls each produce their own verdict, and the results are collected rather than compared. An order is allowed only when nothing triggered anywhere. A single trigger at any level refuses it. This has one consequence worth stating directly, because it is the most common wrong assumption about a hierarchy of settings: > [!WARNING] > > **A narrower control cannot create an exception to a broader one.** Symbol controls do not override account controls, and account controls do not override organization controls. Setting a permissive limit at the symbol level does not grant an exemption from a tighter account limit; both apply, and the tighter one binds. > > To relax a restriction, change or remove the control that imposes it. There is no scope at which a restriction can be overridden from below. Where the same limit is set at more than one level, the effect is the tightest of them. An account maximum order size of 100 with a symbol maximum order size of 50 rejects an order for 75, because the symbol limit triggered. Reversing the two numbers rejects the same order for the same reason, with the account limit triggering instead. Neither level has to know what the other is set to. ### What a refusal tells you An evaluation returns every control that triggered, not just the first one. Each entry names the scope it came from, the control type, and a reason. An order blocked by both a closed trading window and an oversized quantity reports both, so a single call is enough to see everything standing between the order and the broker. The same detail is written to the refusal record, so a supervisory review sees exactly what the caller saw. See [Compliance](/pages/about/compliance) for how refusals are recorded and retained. ### Checking before you submit Anthid can run the full evaluation against a hypothetical order and return the result without sending anything to the broker. It is the same evaluation a real submission gets, so it answers "would this be allowed right now" exactly. This is worth doing on the first order of a session, or any time limits may have changed underneath a system that has been running a while. See the [Controls API reference](/api-reference/controls/overview#control-evaluation) for how to call it. ### What each level can hold Not every control type exists at every level, and the levels are not all on the same plan. | Level | Manual | Risk | Time | Plan | | - | - | - | - | - | | Organization | ✓ | ✗ | ✗ | Professional | | Trading account | ✓ | ✓ | ✓ | Any live plan | | Symbol | ✓ | ✓ | ✓ | Professional | Organization scope is a kill switch and nothing else. There are no organization-wide risk limits or trading windows, so a limit meant to apply everywhere has to be set on each account. The account level is the one that carries every control type and sits on every live plan, which makes it the level to reach for first. ### How quickly a change takes effect Control changes propagate asynchronously to the evaluation service. A successful write does not mean every evaluator has already observed it. This matters in exactly one situation: stopping trading during an incident. Set the switch, then confirm it has taken effect rather than assuming the very next order is already covered. An evaluation check is the way to confirm. Together, these controls create multiple independent layers of protection between your application and the market. As trading infrastructure scales, controls become just as important as execution. Anthid Controls help ensure that strategies remain governed, risk remains bounded, and operational mistakes do not become costly incidents. # Operate orders in the dashboard Source: https://docs.anthid.com/pages/guides/oms-workflow Place an order, trace its commands and broker outcome, and review the account in Anthid's web app. The [Anthid dashboard](https://app.anthid.com) uses the same intent, controls, streaming, and ledger services as your API integration. Select the intended organization and paper or live environment before working with an account. ## Connect and configure Open **Platform > Trading accounts** to link a supported broker account and inspect its connection. Start with paper credentials. Check the broker's supported order types, destinations, and time in force before submission. Open **Platform > Controls** for account and organization controls, and **Platform > Symbols** for instrument controls. Access depends on your role and plan. Controls gate new creates only; disabling a scope does not cancel resting orders. See [Controls](/pages/controls/overview). ## Submit an instruction Organization administrators can open **Trade > Order ticket**. Choose the trading account, symbol, side, and supported order specification, then review the request before submitting. API integrations can submit the equivalent `POST /v1/intents` request from the [quickstart](/pages/intro/quickstart). A successful submission returns an intent ID. It confirms a durable instruction, not a fill. ## Trace the result | Dashboard area | What to inspect | | - | - | | Activity > Intents | Commands, dispatch state, and intent outcome | | Intent detail | The sequenced create, replace, and cancel action chain | | Activity > Orders | Broker order status, filled quantity, and identifiers | | Order detail | The individual order and its related intent | | Activity > Positions | Broker-reported holdings, where supported | | Streams | Incoming intent actions, orders, or positions | A `SENT` action means the command was handed off. Read the resulting order's status and filled quantity to determine execution. A closed order can be cancelled or filled. A replacement carries a complete order specification and references the original intent ID, not the broker order ID. The Intents API also exposes refused submissions at `GET /v1/intents/rejections`. These explain authorization, entitlement, and controls refusals before acceptance; they are distinct from a broker rejecting an accepted instruction. ## Review the book **Analytics > Orders** shows order activity and execution metrics. **Analytics > Exposure** shows holdings valued at broker average cost, rather than current market prices. Ledger queries separate paper and live and are bounded by your plan's history allowance. After a stream interruption, reconcile the ledger's latest state. Primed subscriptions can rebuild retained orders and positions, but do not replay every missed transition. See [Streaming delivery](/api-reference/stream/overview#initial-state-and-live-delivery). # Introduction to Anthid Source: https://docs.anthid.com/pages/intro/introduction What Anthid does, where it sits, and what your firm stops having to build Anthid provides order and execution management through an API and a web dashboard. It sits between the systems your firm uses to trade and the brokers those trades go to. Your strategy decides what to trade; Anthid records the instruction, routes it to a connected broker, and tracks the result. Your systems send an order to Anthid. Anthid checks it against the limits you have configured, passes it to the connected broker, reports back what the broker did with it, and keeps a record of the whole exchange. > **[Start with the quickstart](/pages/intro/quickstart)** > > Connect a paper account and send a first order. ## What sits between you and the broker Connecting a firm's systems directly to a broker means building and then permanently staffing a set of things that have nothing to do with your strategy: somewhere safe to keep broker credentials, code that keeps the connection alive and recovers when it drops, a place to enforce risk limits, a record of what was traded, and a way to see it all. Anthid provides those as a service. Your systems talk to one interface, and Anthid deals with each broker's particulars behind it. ## Trading accounts A trading account is one broker account connected to Anthid. It belongs to your organization, holds the broker credentials in encrypted storage, and is the thing everything else attaches to: orders, positions, limits, and connection health. Anthid keeps each connection open and reports whether the broker is currently reachable, so you can check before trading or show it on a dashboard. ## Orders, and what you asked for Anthid keeps two records where most broker APIs keep one. An **intent** is what you asked for: the instruction as your system sent it, recorded before Anthid contacts the broker at all. An **order** is what came of it at the broker. Keeping them apart is what lets the platform answer the awkward questions. If a broker is unreachable, rejects the order, or acknowledges it and then goes quiet, the instruction still existed, was authorized, and was acted on. A record that only begins once the broker accepts cannot describe that gap. The same separation is what makes an instruction broker-agnostic. You express an order in Anthid's terms, and it is translated into each broker's dialect when it is routed. The instruction reads the same whichever connected broker it goes to. > **[Read more on the model](/pages/concepts/intents)** > > Intents, orders, and positions, and how the three relate. ## Controls Anthid evaluates your configured limits before accepting a new `CREATE` instruction. Replacements and cancellations do not run this evaluation, and disabling trading does not cancel orders already at a broker. You can stop trading across the whole organization with a single switch, or restrict a single account or a single symbol. Limits cover order size, trading hours, and outright enable and disable. Changes take effect without anyone redeploying an application, which matters most on the day you need to stop something quickly. Controls give supervisory constraints somewhere to live besides the trading application itself, and the constraint is enforced whether or not the application remembered to check. > **[How controls work](/pages/controls/overview)** > > The three levels, what each can hold, and how they combine. ## Live updates and history Broker updates reach your systems as Anthid receives them rather than on a polling interval, so an application can react to a fill or a rejection when it happens. Everything also lands in the ledger, a durable record of orders and positions that stays queryable afterwards. That is what reporting, reconciliation, and a supervisory review read from, and order and position records are retained for six years. Your plan determines how much history you can query through the API. See [Limits and entitlements](/pages/concepts/limits). ## What a firm uses this for * Put firm-wide risk limits in front of every order regardless of which system sent it * Block new create instructions across connected accounts with an organization kill switch * Answer what was traded, by which system, and under whose credential * Feed internal dashboards, reporting, and reconciliation from one record * Trade multiple broker accounts, paper and live, through one integration * Add or change brokers without rebuilding the systems that trade through them ## Next steps > **[Concepts](/pages/concepts/intents)** > > The model in more detail: intents, orders, and positions. > **[Brokers](/pages/brokers/overview)** > > Which brokers are connected, what each supports, and how far along the integrations are. > **[Compliance](/pages/about/compliance)** > > What is recorded, how long it is kept, and what it means for a registered firm. > **[API reference](/api-reference/introduction)** > > For your engineers: authentication, endpoints, and the streaming API. # Quickstart Source: https://docs.anthid.com/pages/intro/quickstart Connect a paper account and send a first order, in a few minutes This page takes you from a new account to a first order against paper money. The first four steps are done in the Anthid dashboard and take a few minutes. The last one is a single API call, and is the point where you would hand off to whoever is doing the integration. Paper trading is available on every plan, with no card required, and uses the same API as live trading. Live submission requires a live-trading entitlement, live broker credentials, and support for the chosen order type and destination. ## Get started in four steps Connect a trading account, copy its account ID, create an organization API key, and submit your first order. ### Step 1: Create your Anthid account 1. Sign up for an Anthid account. 2. Log in to your dashboard. ### Step 2: Link a broker account 1. Open **Trading Accounts**. 2. Click **Link Broker Account**. 3. Select the environment and broker, then enter the broker credentials. 4. Submit the form. ### Step 3: Get your `account_id` 1. Find the account in **Connected trading accounts**. 2. Copy its **Account ID**. ### Step 4: Create an API key 1. Open **Organizations**. 2. Create an API key for the active organization. 3. Store the key securely. It is sent as the `x-api-key` header. ## Submit your first order Orders are submitted to Anthid as *intents*: the instruction as you sent it, which Anthid records before it contacts the broker. See [Intents](/pages/concepts/intents) for why the two are kept apart. This buys 10 shares of AAPL at a \$100 limit, good for the day: ```bash theme={null} curl https://api.anthid.com/v1/intents \ -X POST \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "trading_account_id": "", "command": { "type": "CREATE", "payload": { "symbol": "AAPL", "order_venue": "SMART", "order_side": "BUY", "spec": { "Limit": { "price": "100.00", "quantity": "10", "time_in_force": "DAY" } } } } }' ``` Anthid responds with the identifier of the accepted instruction: ```json theme={null} { "intent_id": "019c0538-86f2-7c32-9c1e-6f79cbfdb7b4" } ``` You can also submit from **Trade > Order ticket** in the dashboard as an organization administrator. See the [OMS walkthrough](/pages/guides/oms-workflow) to follow the instruction through orders, streams, and analytics. ## What happens next 1. Anthid checks the order against your organization, account, and symbol controls. 2. If it is allowed, the instruction is recorded and queued for the broker. 3. Order and position updates arrive as the broker reports them, and everything lands in the ledger. A response here does not mean the order filled. It means Anthid accepted and recorded the instruction. Fill state comes from the resulting [order](/pages/concepts/orders). > **[Set up controls first](/pages/controls/overview)** > > Configure limits before pointing anything at a live account. > **[API reference](/api-reference/intents/overview)** > > Request and response shapes, amendments and cancellations, and the full endpoint list.