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

> ## Agent Instructions
> SecId API: use the sandbox base URL https://partners-api.sandbox.securitize.io/investors/v1/onboarding-reliance unless the user explicitly provides production access. Authenticate with OAuth 2.0 client credentials: POST https://partners-api.sandbox.securitize.io/oauth2/token (form fields grant_type=client_credentials, client_id, client_secret, read from environment variables), cache the bearer token for its expires_in (7200 s) and send it as Authorization: Bearer on every call; never print or log the secret or the token.
> Nothing is verified until POST /investors/{investorId}/verification/submit. An individual: create, upload one identity document, submit. A company: create, add its people as key parties (at least one with isBeneficialOwner: true), upload their documents, submit.
> Branch on the HTTP status and the error `code`, never on `message`. A body without `code` comes from the gateway: on a 401 get a new token and send the request again; a 403 means the account is not enabled for this API.
> SecId API addresses: send address.state only when that address's countryCode is US, as the 2-letter state code (NY); omit it for any other country, also when the user has a province or region. Otherwise the call answers 400 validation_error on address.state.
> SecId API individuals in the US: taxId is the person's Social Security number, and it is checked against the name and residential address sent; send them as registered. A mismatch can move the investor to manual-review instead of verified. Sandbox does not run the check.
> Creates are safe to repeat with the same body: after a timeout or a 502, send the identical create again. Document uploads are not: each call stores a new document, so only re-send a document after a 502.
> Poll GET /investors/{investorId} about every 30 seconds, then back off; treat any status you do not recognise as not verified. In sandbox, the webhook investor.kyc.updated also announces every status change, and investor.suitability.updated / investor.dlt_test.updated every change of the suitability and DLT test verificationStatus: verify the signature, deduplicate on sourceEventId and confirm with the matching GET before acting. Token whitelisting is asynchronous too: poll GET /investors/{investorId}/wallets/whitelist until the status leaves processing.
> SecId API tax forms: POST /investors/{investorId}/tax-forms takes multipart/form-data (parts file, formType, signatureDate); never base64 or JSON. Let the HTTP client set content-type with the boundary. At any point of the verification, before or after the investor is verified, only for investors you created (201), one PDF up to 10 MB per call; each upload replaces the form on file, so after a 502 send the same request again. A W-9 can instead go as JSON fields to POST /investors/{investorId}/tax-forms/w9 (TIN as 9 digits, SSN or EIN): it does not replace a form on file (409 tax_form_already_active, details.activeTaxForm), and it requires a certification object (certifiedAt, signatureName, subjectToBackupWithholding false, isUSPerson true, certificationTextVersion w9-sm-2026-10): the investor certifies in your flow on the text published at /secid-api/tax-forms#the-certification, shown without changes; subjectToBackupWithholding true answers 422 business_rule_violation (details.reason backup_withholding_not_supported). Unlike creates, a repeat after success answers 409 tax_form_already_active, not 201: when details.activeTaxForm.formType is W-9, treat it as on file.
> Use synthetic test data in sandbox (@test.invalid emails, fresh ids each run). Never send more than one DLT test attempt per test investor unless the user asks: three failed attempts lock the test permanently.
> The complete contract, with every field, response and error, is the OpenAPI file of the API reference; the Errors page says what to do for each code.
> CET Underlying API: a WebSocket at wss://partners-rt-gw.sandbox.securitize.io/v1/market-maker/stream (TLS only; ws:// hangs with no answer). Sign hello, place_order, cancel_order and submit_crypto_funding with the user's Ed25519 key, read from an environment variable or secrets store; never print or log it. Sign timestamp + "." + type + "." + the exact payload string you send: serialise the payload once and embed that same string in the frame.
> CET Underlying API: send a unique requestId of at most 32 characters (a UUID without hyphens) on every message and match replies on it; frames without a requestId are pushes. Every amount is a decimal string, never a JSON number; compare amounts as decimals. Branch on the error code, never on message.
> CET Underlying API: an ack to place_order means taken, not filled. externalRef is the idempotency key; after an ack with status UNKNOWN or an UPSTREAM_UNAVAILABLE error, call get_order by externalRef before resending. Updates are delivered at most once: after any reconnect, read working orders back with get_order. Every order is placed by quantity. A buy must be a LIMIT order: quantity × limitPrice plus fees is held against your own available cash (get_cash_balance_v2), and a buy it does not cover, a MARKET buy or a notional order is refused (INVALID_ORDER). A sell must be placed by quantity, after its CETs reach the redemption wallet, or it is refused. mint_updated and burn_updated push mints and burns once they are on chain; after a gap, read them with get_mints and get_burns. The complete contract is the AsyncAPI file of the API reference.
> Stocks API (Beta): documented ahead of its release through the partner gateway; paths, fields and errors can change. Base URL https://partners-api.sandbox.securitize.io/stocks/v1. Send x-api-key on every call and investorId on every call about an investor. Read the key from an environment variable; never print or log it, and never call the API from frontend code.
> Stocks API: an investor can trade only once the SecId API shows them verified, with verified suitability (and a passed DLT test in the EEA), and once their wallet on the stock's chain is linked with their signature of the agreement message, signed verbatim. There is no whitelisting call: the first trade whitelists the wallet.
> Stocks API: orders are fill or kill. GET /stocks/realtime-price, prepare within about 20 seconds (signedPrice), have the investor's wallet sign the returned transaction unchanged and sign-only (never send it), then submit it once with POST /stocks/execute-signed-transaction within 120 seconds. Amounts are raw base units as strings; USDC has 6 decimals. The outcome is on chain: follow transactionHash.
> Stocks API: production access requires a UI audit by Securitize Compliance of the live screens: the disclosures and the market data shown, the NBBO included.
> Corporate Actions API (Beta, 0.x): in development and subject to change; paths, fields, enumerations and event names can change before 1.0.0. Base URL https://partners-api.sandbox.securitize.io/corporate-actions/v1. Get a bearer token with OAuth 2.0 client credentials at POST https://partners-api.sandbox.securitize.io/oauth2/token (no scope, 2 hours), cache it, and send Authorization: Bearer on every call. Read the client id, secret and the webhook signing secret from environment variables; never print or log them, and never call the API from frontend code.
> Corporate Actions API: webhooks arrive signed with the Standard Webhooks scheme under the svix-id, svix-timestamp and svix-signature headers (HMAC-SHA256 over id.timestamp.raw body, base64, v1 prefix; the secret is base64 after whsec_). Verify before parsing, reject a timestamp more than 5 minutes off, answer 2xx within 10 seconds. Delivery is at least once and can be out of order: deduplicate on sourceEventId, keep the highest version per object (objectId), order by sequence per stream.id, and replace the object with data (never merge).
> Corporate Actions API: treat an unknown caType, reasonCode, eventType or enum value as informational, never as an error. A restriction ends only with ca.restriction.removed; expectedEnd is informational. marketStatus UNAVAILABLE means do not trade; the reason field carries a restriction reasonCode or a market reason such as PRE_MARKET. GET /tokens?status= filters by marketStatus (OPEN, CLOSED, UNAVAILABLE), not by the token's ACTIVE status.
> Corporate Actions API: the event routes are at https://partners-api.sandbox.securitize.io/webhooks/v1 (not under /corporate-actions/v1) and cover every webhook family sent to the partner; filter with eventTypes=ca.* for corporate actions only. After an outage call POST /events/recover with since at the start of the outage (resends failed deliveries only), or page GET /events?since= (newest first, iterator paging, 90 days), then read state with GET /tokens/{tokenId}/status and GET /corporate-actions/{sourceCaId}. In sandbox, POST /sandbox/events with a scenario and one of your tokenIds publishes a scripted sequence at once; order the events by sequence, not by arrival.
> Investor Activity API: base URL https://partners-api.sandbox.securitize.io/v1. Authentication is OAuth 2.0 client credentials only (no API key): POST https://partners-api.sandbox.securitize.io/oauth2/token (no scope, 2 hours), cache the token, and send Authorization: Bearer on every call. The token identifies the investor account; there is no investor id to pass. Read the client id, secret and the webhook signing secret from environment variables; never print or log them, and never call the API from frontend code.
> Investor Activity API: every amount is an integer string in base units with a sibling decimals (human = amount / 10^decimals); parse with BigInt or a decimal type, never a float. Cash amounts (deposits, confirmations, payouts) are in the minor units of currencyId (1 = USD, decimals 2); token amounts (transactions, mintings, holdings) in the token's base units. Never add cash to tokens.
> Investor Activity API: reconcile a subscription by grouping GET /deposits?tokenId= on tokenIssuanceId and matching the transaction in GET /transactions?tokenId= with the same tokenIssuanceId (null on a deposit means not issued yet; null on a transaction means not an issuance). Each issuance has a deposit movement (cash in) and a liquidation movement (that cash applied to the tokens) for the same amount: count the cash once, by direction=deposit, never by adding every movement. Page with offset and limit (max 100) until offset + meta.length reaches meta.total; there is no tokenIssuanceId filter. Reconcile a redemption by txHash (transfer, investor.redemption.pending, payout sourceTransaction) and by the redemption's data.id across pending, confirmed, payout.initiated and payout.completed.
> Investor Activity API: webhooks arrive signed with the Standard Webhooks scheme under the svix-id, svix-timestamp and svix-signature headers (HMAC-SHA256 over id.timestamp.raw body, base64, v1 prefix; the secret is base64 after whsec_). Verify before parsing, reject a timestamp more than 5 minutes off, answer 2xx within 15 seconds. The body is {eventType, eventId, timestamp, data}: deduplicate on eventId (not svix-id), upsert by data.id, never move a record back to an earlier step, and treat an unknown eventType, processType or paymentMethod as informational. In webhooks a field with no value is absent; in endpoint answers it is null. After an outage page GET /webhook-events (newest first, iterator paging, optional eventType) back to the start of the outage.
> SecId hosted onboarding: instead of the API, a partner can send an investor to https://stocks.securitize.io/partners?tokenId=<stock token id>&partnerUrl=<return URL> (tokenAddress may replace tokenId; partnerUrl must be an absolute https URL, URL-encoded). Securitize runs identity verification, the investment profile and the wallet there. Nothing is appended to partnerUrl on return: check the wallet on chain before trading (Beta on Solana).

# Changelog

> Changes to the CET Underlying API and these docs, newest first.

**November 2026**

Fund your cash with crypto on Solana

* **`submit_crypto_funding` on Solana.** A USDC deposit sent on Solana is credited like one on an EVM chain: send its base58 signature as `txHash`, as it is (signatures are case-sensitive), with `chainId` `101` (or `901` in sandbox). `get_crypto_funding` lists Solana in `depositTo` where it is offered, with the USDC mint as `tokenAddress`, and your Solana wallets in `fromWallets` with `namespace: "solana"`. Nothing changes for EVM deposits. See [Fund your cash with crypto](https://developers.securitize.io/cet-underlying-api/positions-and-cash.md#fund-your-cash-with-crypto).

**November 2026**

ISIN and CUSIP in get\_symbols

* **`get_symbols` names each chain's `isin` and `cusip`** (2026-11-04): the identifiers of the security behind that chain's CET, beside its `tokenAddress`. They are per chain, since each chain is its own CET. A chain without one leaves the field out rather than sending `null`. Nothing else in `get_symbols` changes.

**November 2026**

A reference the broker refused

* **Correction: a reference the broker refused cannot be used again the same day** (2026-11-04). These pages said the reference of an order the broker rejected could be reused. The broker keeps it for the rest of the day, so resending answers `INVALID_ORDER` as a duplicate: send the order under a new `externalRef`. A reference refused by Securitize before the order reached the broker (your own cash, the symbol, the order's shape) is still usable. An order the broker refused as it was placed was never created, so `get_order` answers `ORDER_NOT_FOUND` for it.

**November 2026**

Time in force and tick size

* **Only `DAY` is supported as `timeInForce`** (2026-11-04). The contract listed `GTC`, `IOC` and `FOK`, but the broker does not offer them through its API: `IOC` and `FOK` answer `INVALID_ORDER`, and a `GTC` order is refused by the broker after its `ack`. Nothing changes for an order that sends `DAY` or leaves `timeInForce` out.
* **Tick size for `limitPrice`.** A price of 1.00 or more must be in whole cents, at most 2 decimal places; the broker refuses a finer one as sub-penny. Only a price under 1.00 may use 4. See [Limits on an order](https://developers.securitize.io/cet-underlying-api/orders-and-statuses.md#limits-on-an-order).

**November 2026**

Fund your cash with crypto

* **New read: `get_crypto_funding`** (2026-11-03). Where to send USDC to fund your own cash: `depositTo`, Securitize's wallets, one per chain and token with the token's contract and the confirmations a deposit needs, and `fromWallets`, your own wallets a deposit must come from. When crypto funding is not enabled for you, `enabled` is `false` and `depositTo` is empty. Like every read, it is not signed and a read-only key may send it, and it is served over HTTP too, as `GET /v1/market-maker/crypto-funding`.
* **New: `submit_crypto_funding`.** Send the `chainId` and `txHash` of a USDC deposit you sent to one of `depositTo`, and it is credited to your own cash in USD, one for one, once the transaction succeeded and is final. EVM chains only. Signed like `place_order`; a read-only key answers `NOT_PERMITTED`. A transaction is credited once, whoever sends it, and sending it again answers the same credit. The credit shows in `get_cash_movements` and `get_cash_balance_v2`; it is not pushed as `cash_movement`. Two new error codes: `DEPOSIT_PENDING`, not on chain yet or not final, and `DEPOSIT_REFUSED`, not credited as sent. See [Fund your cash with crypto](https://developers.securitize.io/cet-underlying-api/positions-and-cash.md#fund-your-cash-with-crypto).

**October 2026**

Every redemption balance at once

* **New read: `get_redemption_balances`** (2026-10-28). Your redemption balance in every CET you can trade, in one read: the `ack` carries `balances`, one entry per symbol and chain with the same `deposited`, `burned`, `reserved` and `available` as `get_redemption_balance`, sorted by symbol and then chain. `symbol` and `chainId` are optional and narrow the list; nothing matching is an empty list, not an error. Like every read, it is not signed and a read-only key may send it, and it is served over HTTP too, as `GET /v1/market-maker/redemption-balances`. `get_redemption_balance` is unchanged. See [Reads over HTTP](https://developers.securitize.io/cet-underlying-api/reference/http-reads.md).

**October 2026**

Reads over HTTP

* **New: the reads as signed `GET` requests.** Your redemption balance, an order, mints, burns, your cash and its movements answer over HTTP too, with no session: the query parameters are the message's payload fields, and the body is the `ack`'s payload. Every request carries your public key, a timestamp and an Ed25519 signature over `timestamp.GET.path-and-query`, with the same key you sign `hello` with; a read-only key is enough. Failures answer the socket's codes with an HTTP status. See [Reads over HTTP](https://developers.securitize.io/cet-underlying-api/reference/http-reads.md).

**October 2026**

Fund your cash in sandbox

* **New in sandbox: `sandbox_deposit` and `sandbox_withdraw`** (2026-10-26). They move USD into and out of your own cash, so you can fund yourself and test the cash flow end to end. Signed like `place_order`, with an `amount` and your own `clientRef`, which never moves money twice. Each is booked about a minute later and pushed as `cash_movement`. **Sandbox only**: in production they answer `MALFORMED`. See [Sandbox funding](https://developers.securitize.io/cet-underlying-api/sandbox-funding.md).

**October 2026**

Contract and guides aligned

* **`quantity`, `notional` and `limitPrice` patterns** now reject zero and leading zeros in the contract too, as the server always has.
* **Reconnect on any close**, not only `1001`; a session that cannot be opened for a moment (`UPSTREAM_UNAVAILABLE`) or a clock off by more than 30 seconds keeps the socket: send a new `hello`.
* **A symbol your account does not trade answers `INVALID_ORDER`**, mistyped or not; `INVALID_SYMBOL` is left for a symbol the broker no longer recognises.

**October 2026**

get\_positions retired

* **`get_positions` is retired.** It reported the broker account as a whole, which other clients trade through too, rather than what is yours. Your shares are the CETs in your wallet; `get_mints` and `get_burns` say how they got there, and `get_cash_balance_v2` what your orders cost and brought in.

**October 2026**

Your own cash

* **New read: `get_cash_balance_v2`.** Your own cash, per currency: what you deposited and withdrew, the fees charged to you, what your orders bought and sold, your balance after all of it (`netBalance`), what your working buys hold (`committed`), and `available`, what a new buy can spend. See [Your own cash](https://developers.securitize.io/cet-underlying-api/positions-and-cash.md#your-own-cash).
* **New read: `get_cash_movements`**, the deposits, withdrawals and fees behind your balance, newest first, and a new push, **`cash_movement`**, when one is recorded or cancelled.
* **A buy is paid from your own cash.** It must be a `LIMIT` order by `quantity`, and `quantity × limitPrice` plus its fees is held against your `available` before it reaches the broker. A buy that cash does not cover, a `MARKET` buy and an order by `notional`, buy or sell, answer `INVALID_ORDER`. With orders in money gone, so is their 100,000 USD ceiling.

**October 2026**

Redemption balance

* **New read: `get_redemption_balance`.** Send a symbol and a `chainId`, as `get_symbols` lists them, and the `ack` carries your own balance in that CET's redemption wallet: `deposited`, `burned`, `reserved` and `available`. Like every read, it is not signed and a read-only key may send it.
* **A sell is checked against your redemption balance.** The redemption wallet is shared by every account that sells the CET, so each transfer to it is credited to the account whose wallet sent it, and a sell may only draw on what was credited to it and not already held by its other sells. A sell for more answers `INVALID_ORDER` with your figures. Read `get_redemption_balance` after a transfer to know when it has been credited. See [Sell shares and redeem CETs](https://developers.securitize.io/cet-underlying-api/sell-shares.md#1-send-the-cets-to-the-redemption-wallet).

**October 2026**

Redemption wallet

* **New read: `get_redemption_address`.** Send a symbol and a `chainId`, as `get_symbols` lists them, and the `ack` carries the CET's `tokenAddress` and its `redemptionAddress`: the wallet to send the CETs to before you sell. Like every read, it is not signed and a read-only key may send it. A symbol and chain you cannot trade answers `INVALID_SYMBOL`. See [Sell shares and redeem CETs](https://developers.securitize.io/cet-underlying-api/sell-shares.md#1-send-the-cets-to-the-redemption-wallet).

**October 2026**

Fees on orders

* **`get_order` carries `fees` and `feeTotal`**: the fees charged on the order's trades, one per trade with its status, and their sum. Fees are charged after the fill, when each trade reaches the broker's ledger, so the list can grow after the order has filled. Fees charged before this change are not listed. See [Fees](https://developers.securitize.io/cet-underlying-api/orders-and-statuses.md#fees).

**October 2026**

Generating a keypair

* **[Sandbox and authentication](https://developers.securitize.io/cet-underlying-api/sandbox-and-auth.md#generate-a-keypair) shows how to generate the Ed25519 keypair** with OpenSSL, print both keys in base58, and what to send Securitize to register it. Any Ed25519 keypair works, whatever chain your CETs are minted on.

**October 2026**

Burn transaction hash

* **Burns carry `txHash`**, the hash of their transaction on its chain, already on the first `burn_updated`, and `get_burns` takes it as a filter. Burns from before this change have none. See [The transaction hash](https://developers.securitize.io/cet-underlying-api/mints-and-burns.md#the-transaction-hash).
* **A burn's memo is written on chain on Solana**, as a mint's is. The docs said it was not: a burn can be found from its transaction, by `txHash`, or on Solana by the `traceId` at the end of its memo.

**October 2026**

Mint transaction hash

* **Mints carry `txHash`**, the hash of their transaction on its chain, and `get_mints` takes it as a filter. Mints from before this change have none. On an EVM chain the mint transaction carries no memo, so this is the way from a transaction to its mint there. See [The transaction hash](https://developers.securitize.io/cet-underlying-api/mints-and-burns.md#the-transaction-hash).
* **A mint is usually pushed up to three times.** `mint_updated` usually comes again within a minute of a mint landing, carrying its `txHash`, and the same snapshot can occasionally arrive twice. Overwrite the mint you hold by `traceId`, as before.

**October 2026**

Selling after a transfer

* **[Sell shares and redeem CETs](https://developers.securitize.io/cet-underlying-api/sell-shares.md#1-send-the-cets-to-the-redemption-wallet) says when the CETs count.** A transfer to the redemption wallet counts once it is confirmed on chain and recorded by Securitize; a sell placed before then answers `INVALID_ORDER` and can be sent again a few seconds later.

**October 2026**

Symbols and chains

* **New read: `get_symbols`.** It lists the symbols you can trade and, for each one, the chains its CETs can be minted on, with the token address on each chain. Like every read, it is not signed and a read-only key may send it.
* **`place_order` takes an optional `chainId`.** Send it when `get_symbols` lists the symbol on more than one chain; without it such an order answers `INVALID_ORDER`. A symbol on a single chain needs no `chainId`.
* **Responses carry `chainId`.** The `place_order` `ack`, `get_order`, `get_mints`, `get_burns` and every push carry `chainId` beside `tokenAddress`: the chain of the CET, as `get_symbols` lists it.
* **`externalRef` is at most 39 characters** on `place_order` (previously 47). `get_order` still finds orders placed with a longer reference.

**October 2026**

Developer portal

The CET Underlying API documentation is published on this portal: the API reference for every message, the guides to buying and selling shares and reconciling the CETs minted and burned, and the material coding agents read.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.