Skip to main content
Use gRPC when you want generated clients, strongly typed request/response contracts, or backend-to-backend integration. Monaco publishes the protobuf module to BSR at buf.build/monaco-sdk/api.

Endpoint

Protobuf Module

See the gRPC protobuf reference for the curated protobuf file, package, service, and message reference.

Services

Wallet authentication, session refresh, and session revocation.
Order entry, cancellation, replacement, batch operations, order lookup, and conditional TP/SL order management.
Profile, balances, ledger movements, funding payments, sub-accounts, user trades, and portfolio analytics.
Public market data, candles, screener data, perp market config, funding, open interest, mark price, and index price.
Public orderbook snapshots. GetOrderbookRPC is a gRPC-only wrapper over the same response type.
Public trade history, single-trade lookup, and authenticated user trade history.
Isolated-margin account summaries, collateral availability, collateral transfers, movements, and order-risk simulation.
Margin positions, close flows, risk checks, margin adjustments, position history, and TP/SL attachment.
Application configuration (session-authenticated) and public, client_id-selected users, balances, orders, movements, and stats.
Delegated-agent registration, owner lookup, revocation, and delegated-session creation.
Withdrawal initiation, pending-withdrawal listing, and signed calldata lookup.
Fee simulation for prospective orders.
Testnet token minting for authenticated users.
Public whitelist applications are closed. SubmitWhitelist returns PERMISSION_DENIED without changing applicant data.
Public service health check.

Generate a Client

Authenticate requests

Monaco is tokenless. There is no access token to attach: you generate an ed25519 session keypair locally, register its public key during challenge/verify, and sign every authenticated request with the session private key. The server verifies the signature against the stored public key (grpc-api/src/auth/interceptor.rs); public market-data RPCs need no signature.
grpcurl cannot authenticate: the signature is taken over the prost-encoded request body, which grpcurl has no way to produce. Use a generated gRPC client (any language) and sign each call, as below.

The signing contract

Each authenticated unary call carries three metadata entries: The canonical signing string is four newline-separated lines — note the fixed POST, the full leading-slash method path, and the SHA-256 of the exact serialized request bytes:
Binding the method path matters: empty-body RPCs all encode to the same bytes, so without the path a signature captured from a benign call could be replayed against a destructive one.

Sign and place an order

The excerpts below are the signing mechanics (@grpc/grpc-js); concatenated, they are a minimal signed create/cancel once you supply a session key and a trading-pair id. They load a descriptor set rather than .proto files at runtime. Build one from the public BSR module — no checkout of the Monaco repository is required:
Rebuild it whenever you pick up a newer version of the module. To verify your signer against fixed inputs before you send anything, use the golden signing vectors below.
1

Open a session

Generate a session keypair and register its public key: pass session_public_key to Challenge, sign the returned message with your wallet, then call Verify. No token is returned — the session is the registered public key, and Verify returns an expires_at (see Session expiry and refresh). The same handshake over REST — POST /api/v1/auth/challenge then POST /api/v1/auth/verify, with the request and response fields spelled out — is in the API reference, and Wallets & Auth covers wallet setup.
2

Sign every authenticated call

Serialize the request with the method’s own serializer (the exact prost bytes on the wire), hash them, build the canonical string, and sign it.
3

Load the client, then create and cancel an order

@grpc/proto-loader cannot parse the source protos (gnostic aggregate options), so load a prebuilt FileDescriptorSet — make descriptors-gen from a repo checkout, or buf build buf.build/monaco-sdk/api -o monaco-descriptors.binpb against the public schema registry.
Each call re-signs: the timestamp and body hash differ per request, so signatures are never reused.

Session expiry and refresh

Verify and Refresh both return expires_at, a Unix timestamp (seconds) for when the session lapses. Track it and refresh proactively, before it passes: call AuthService.Refresh with an empty RefreshRequest, signed exactly like any other call but over the method path /monaco.api.auth.AuthService/Refresh. It returns a fresh expires_at.
Once a session has already expired (or been revoked), Refresh itself returns UNAUTHENTICATED; at that point you must re-run challenge/verify to register a new session key. Do not loop-retry the same signed request against a persistent UNAUTHENTICATED — it will never succeed. A stale or far-future x-monaco-timestamp is likewise rejected, so always sign with the real current time in milliseconds, not a cached value.

Error handling

CreateOrderRequest.idempotency_key makes a create safe to replay with the same normalized payload for 24 hours after acceptance. Generate and persist the key before signing the first request, then reuse that exact key and payload after an ambiguous outcome. A different payload returns ALREADY_EXISTS; after 24 hours, the same key may place a new order. Without the retained key — or after it expires — never blindly retry a CreateOrder, or you risk a duplicate order. Reconcile on the order WebSocket stream rather than ListOrders: that read path is served asynchronously from a replica and can omit an order that did land, and an order rejected before acceptance is never written to it at all (one rejected after acceptance is persisted with status REJECTED). The stream is not replayable — it carries no sequence number to backfill from — and its fan-out is non-persistent Core NATS, so the service can re-establish an ended upstream subscription without closing your socket. Subscribe before you send, but treat only a matching event as an answer: silence is never evidence the order did not land, and no condition you can observe makes it so. Absent positive evidence, hold rather than retry. Setting a clientOrderId makes the stream events self-identifying, but note it is claimed only while an order rests — see Client Order IDs. ReplaceOrder behaves differently: it names the original order id, and the sequencer revalidates that original at sequence time, so once one replacement succeeds a retry is rejected with NOT_FOUND rather than placing a second order. That code is ambiguous on its own — the original may instead have filled or been cancelled — so reconcile to find the replacement’s new order id rather than to rule out a duplicate.

Golden signing vectors

Deterministic vectors — request, serialized bytes, timestamp, canonical string, and signature for a spot and a margin CreateOrder. They are the source of truth for the signing contract and are exercised by both the TypeScript example tests and a server-side Rust test; the copy below is drift-gated against the one those tests load, so it cannot fall out of date. No private key is shipped, so an implementation in any language can reproduce the byte-identical request bytes and canonical signing string and verify each published signature against signerPublicKey — the check the gateway itself performs. Producing your own signatures needs your own session key; these vectors prove the bytes and verification, not re-signing. Pin them in your signer’s unit tests: serialize each request with a descriptor set built from buf.build/monaco-sdk/api, and assert you reproduce serializedHex, bodySha256Hex, and signingString before checking signature against signerPublicKey.
Golden signing vectors
The signingString values above render \n as literal backslash-n inside JSON; the real canonical string joins its four parts with newline characters. Signatures are ed25519 over the UTF-8 bytes of that string, hex-encoded.