API Access
How to reach Native Core — the endpoints, environments, API wallets, signing, and limits.
Native Core exposes two REST endpoints and one WebSocket endpoint:
POST /info— reads. One endpoint for all public reads, dispatched by a top-leveltypefield (market metadata, order books, balances, order status, fills).POST /trade— writes. One client-signed action per call (order,cancel,cancelAll,modify,batch, and the owner-signedwithdraw/settle/repay/approveAgent/revokeAgent)./ws— streaming. Push channels for books, trades, fills, and order updates, plus the same/infoand/tradebodies sent over the socket. See WebSocket.
This page is the front door for anyone integrating directly with Native Core: market makers, trading bots, AI-agent builders, and aggregators.
There are two ways to integrate:
Call the API directly — the endpoints documented on this page, plus the WebSocket surface when you need push instead of polling. Full control, any language.
Use the Native Core Python SDK — a thin, typed client that wraps both endpoints and handles
/tradesigning, nonces, and the synchronous outcome (reconciling atimeoutbycloid) for you.
Signing /trade by hand means building a canonical binary payload with recoverable secp256k1 signatures and per-signer monotonic nonces — fully specified below for building your own client. The Python SDK implements it for you.
Environments
The API is served over HTTPS. Native Core runs on mainnet, with a testnet for integration and testing. Each environment has its own base URL and signing chain id:
Environment
Base URL
WebSocket
network
Chain id (signing)
Mainnet
https://api.native.org
wss://api.native.org/ws
mainnet
696969
Testnet
https://api-test.native.org
wss://api-test.native.org/ws
testnet
969696
The chain id is folded into every signed /trade payload and must match the environment you target — sign for the wrong one and the API recovers a different authority and rejects the write (see Transaction signing). The examples below use mainnet:
API_URL=https://api.native.orgYou fund a trading account by depositing assets from your main wallet. See the access model below for depositing and creating the API wallet.
Access model — API wallets
Writes are authorized by an API wallet: a protocol-level agent key scoped only to placing and cancelling orders. An API wallet can trade your account's balance, but it can never move funds off Native — deposits, withdrawals, and agent approval all require your main wallet. That scoping (and the fact that it is revocable) is what makes it safe to run in an unattended bot.
An API wallet is just an agent keypair you generate locally, authorized on one of your account's agent slots (0–3) by a single owner-signed approveAgent. Set it up directly against the API — no browser required:
Generate an agent keypair locally (any secp256k1 key). The private key is your only signing secret; it never leaves your process. Its 20-byte address is the
agent.Fund the account. Deposit a supported asset from your main wallet — the per-chain deposit contracts are in
accountingDepositContracts, and the supported assets, theirasset_ids, and minimums inassets/accountingWithdrawTokens. Your trading account is created on the first deposit, which pays a one-time activation fee. Deposit & Withdraw has the full flow for both directions.Approve the agent. Your main wallet signs one
approveAgentunderauth_scheme:"eip712"and youPOST /tradeit:
The typed-data fields are in Transaction signing; the action reference is approveAgent. After approval, read the slot's epoch from userAgents — each entry is {slot_id, agent, epoch} — and send that value as the agent_epoch envelope field on agent-signed /trade writes.
Prefer a UI? The Native web app does these same steps for you — connect your main wallet and it generates the agent key, submits the approveAgent, and hands you a one-time connection bundle. Either way you end up holding the same three values:
accountAddress is used only locally to identify the account you trade on — it never goes on the wire. the API recovers the signer from the signature. For the API wallet's authority model, replay protection, and safety rules, see:
Making requests
Both endpoints take a JSON body over POST and return JSON. POST /info is unauthenticated; POST /trade carries a signed action.
POST /info (reads)
Dispatch on the top-level type. Minimal example — list the tradable markets and their precision:
The full per-type reference (every query, its parameters, and its response shape) is here:
POST /trade (writes)
One signed action per call. Minimal example — a resting limit bid (the signature is a secp256k1 signature over the canonical binary payload, not over this JSON text, so you cannot type it by hand — the SDK produces it):
The full per-action reference (envelope fields, every action type, and the constraints) is here:
POST /tradeWebSocket (streaming)
Connect and subscribe — no authentication, no headers:
Nine push channels cover books, trades, mids, fills, order updates, and balances, and a post method carries the two request bodies above over the same connection. Every channel, payload, and limit is here:
Authentication & signing
Every /trade write is a client-signed transaction. The API reconstructs a canonical unsigned binary payload from action, nonce, agent_epoch, and expires_after_ms, then verifies the recoverable secp256k1 signature over that exact payload — the transaction authority is the recovered signer. The owner address is never sent.
Trading actions (
order,cancel,cancelAll,modify,batch) use the default legacy binary scheme (auth_scheme: "legacy") and are signed by the API wallet key.Owner
/tradeactions (withdraw/settle/repay) are EIP-712 (auth_scheme: "eip712") and are signed by your main wallet — not with the API wallet, and not part of a bot's hot path.Agent approval (
approveAgent/revokeAgent) is also a main-wallet EIP-712 signature — it is how you create or revoke the API wallet. Do it in the Native web app or sign it yourself — both are/tradeaction types the API accepts directly.
The exact payload layout, encoding rules, and EIP-712 typed-data schemes are here:
Transaction SigningNumbers
Native Core executes on integers only. Public order / modify payloads accept human decimal strings for price and quantity; the write path converts them to raw atoms using each market's price_decimals and base_quantity_decimals (max_price_sig_figs is enforced at execution, not on the write path). Send prices and sizes as strings, never floats, and respect each market's precision — an out-of-precision value is rejected, not silently rounded.
Request correlation
POST /trade accepts an optional x-trace-id request header and echoes it back in the response, so you can line a write up with the API's own logs. If you omit it or send an invalid value, the API mints one; a /trade response always includes an x-trace-id. POST /info does not process or return x-trace-id.
Rate limits & errors
Two limiters apply, both returning HTTP 429 with error.code: "RateLimited":
Per IP — 1 request/second to
/infoand 1 request/second to/trade, as two independent budgets, so a burst of reads never starves your writes. Each is a token bucket holding one second of allowance. Over-quota reads return{"error":{"code":"RateLimited","message":"ip rate limit exceeded, retry after <ms>ms"}}; over-quota writes come back in the trade-response shape with anerror.retry_after_mshint.Per signer — writes are additionally limited to 1000 requests/second, keyed on the recovered authority over a 1-second sliding window, so one API wallet is one bucket.
For a single integration the per-IP budget is the one that binds: it applies before any signature is checked, and 1000 writes/second per signer is only reachable across many source addresses.
Request bodies over 64 KiB (/info) or 256 KiB (/trade) are rejected with HTTP 413.
A post request sent over the WebSocket charges the same per-IP budget as the equivalent HTTP call and is answered with a 429 string in the reply envelope — the socket is a convenience, not extra quota.
The /trade error model keys on the response body, not the HTTP status — the API returns the same trade-response shape for HTTP 200 / 400 / 429 / 503 / 504, so a business rejection is data, not a transport error. Branch on submission_status, never the status line. What each outcome means and how to act on it is the Handle outcomes & timeouts playbook; every code and its fix is in:
Next step
The Python SDK wraps both endpoints, signs /trade for you, and turns each of the outcomes above into a decision-ready field.
Last updated