API Overview
Response Envelope
All API responses use a standard envelope. Successful responses have success: true with a data field. Error responses have success: false with an error field.
// Success{ "success": true, "data": { ... }}
// Error{ "success": false, "error": { "code": "VALIDATION_ERROR", "message": "Realm name is required", "errorId": "err_a1b2c3d4" }}The errorId field is included on server errors for log correlation and may be omitted on client errors.
HTTP Status Codes
| Code | Meaning |
|---|---|
200 | Success |
201 | Created — resource successfully created |
400 | Bad Request — validation error in your input |
401 | Unauthorized — missing or invalid authentication |
403 | Forbidden — authenticated but insufficient permissions (scope) |
404 | Not Found — resource doesn't exist or isn't accessible |
409 | Conflict — duplicate resource or invalid state transition |
500 | Internal Server Error — unexpected failure |
502 | Bad Gateway — upstream service error (e.g., exchange unavailable) |
503 | Service Unavailable — dependency temporarily down |
Auth Endpoints
Sign Up
POST/api/v1/auth/signup
Create a new builder account. Returns a short-lived JWT access token (15 min) and an opaque refresh token (30 days).
Request Body
emailstringrequiredpasswordstringrequiredorgNamestringrequiredResponse
{ "success": true, "data": { "token": "eyJhbGciOiJIUzI1NiIs...", "refreshToken": "arca_rt_abc123...", "expiresAt": "2026-03-04T12:15:00.000000Z", "builder": { "id": "54248205-1cc7-4437-a76c-95d2b7bf07e0", "email": "you@company.com", "orgName": "Acme Trading", "orgId": "org-uuid", "role": "owner" } }}Sign In
POST/api/v1/auth/signin
Authenticate an existing builder. Returns a short-lived JWT access token (15 min) and an opaque refresh token (30 days).
Request Body
emailstringrequiredpasswordstringrequiredResponse
{ "success": true, "data": { "token": "eyJhbGciOiJIUzI1NiIs...", "refreshToken": "arca_rt_abc123...", "expiresAt": "2026-03-04T12:15:00.000000Z", "builder": { "id": "54248205-1cc7-4437-a76c-95d2b7bf07e0", "email": "you@company.com", "orgName": "Acme Trading", "orgId": "org-uuid", "role": "owner" } }}Sign In with Google
POST/api/v1/auth/google
Sign in with Google OAuth. Submit the Google ID token obtained from the Google Sign-In flow. Returns the same AuthResponse as signin (token, refreshToken, expiresAt, builder). No auth required.
For new accounts, you must provide orgName to create an organization. If omitted, the API returns error code new_account_requires_org.
Request Body
googleIdTokenstringrequiredorgNamestringResponse
{ "success": true, "data": { "token": "eyJhbGciOiJIUzI1NiIs...", "refreshToken": "arca_rt_abc123...", "expiresAt": "2026-03-04T12:15:00.000000Z", "builder": { "id": "54248205-1cc7-4437-a76c-95d2b7bf07e0", "email": "you@gmail.com", "orgName": "Acme Trading", "orgId": "org-uuid", "role": "owner" } }}Refresh Token
POST/api/v1/auth/refresh
Exchange a valid refresh token for a new access token and a new refresh token (rotation). The old refresh token is invalidated.
Request Body
refreshTokenstringrequiredResponse
{ "success": true, "data": { "token": "eyJhbGciOiJIUzI1NiIs...", "refreshToken": "arca_rt_xyz789...", "expiresAt": "2026-03-04T12:30:00.000000Z" }}Logout
POST/api/v1/auth/logout
Revoke a refresh token, ending the session. The access token remains valid until it expires (max 15 min).
Request Body
refreshTokenstringrequiredResponse
{ "success": true, "data": { "loggedOut": true }}Get Profile
GET/api/v1/auth/meJWT
Retrieve the authenticated builder's profile.
Response
{ "success": true, "data": { "id": "54248205-1cc7-4437-a76c-95d2b7bf07e0", "email": "you@company.com", "orgName": "Acme Trading", "orgId": "org-uuid", "role": "owner" }}Mint Scoped Token
POST/api/v1/auth/tokenJWT / API Key
Mint a scoped JWT for an end-user. The token is locked to a single realm and carries IAM-style policy statements. Call this from your backend and pass the resulting token to the end-user's frontend. See Scoped Tokens & Permissions for full details on actions, resource patterns, and enforcement, and Architecture Patterns for guidance on which operations to expose via scoped tokens.
Request Body
realmIdstringrequiredsubstringrequiredscopeobjectrequiredstatements array. Each statement has effect ("Allow" or "Deny", default "Allow"), actions (action strings or aliases), and resources (path patterns).expirationMinutesnumber60.Response 201 Created
{ "success": true, "data": { "token": "eyJhbGciOiJIUzI1NiIs...", "expiresAt": "2026-02-14T15:00:00Z" }}Example: Read-only token (recommended default)
curl -X POST http://localhost:3052/api/v1/auth/token \ -H "Authorization: Bearer arca_78ae7276_..." \ -H "Content-Type: application/json" \ -d '{ "realmId": "6d25623e-...", "sub": "alice", "scope": { "statements": [ { "effect": "Allow", "actions": ["arca:Read"], "resources": ["*"] } ] }, "expirationMinutes": 30 }'Realm Endpoints
All realm endpoints require JWT authentication. Builders can only access their own realms.
Create Realm
POST/api/v1/realmsJWT / API Key
Request Body
namestringrequiredtypestring"demo" (default) or "production".descriptionstringResponse 201 Created
{ "success": true, "data": { "id": "6d25623e-597e-4815-8bfa-6911b38c2079", "builderId": "54248205-1cc7-4437-a76c-95d2b7bf07e0", "name": "Development", "slug": "development", "type": "demo", "description": "My development environment", "createdAt": "2026-02-11T21:12:52.529Z", "updatedAt": "2026-02-11T21:12:52.529Z" }}List Realms
GET/api/v1/realmsJWT / API Key
Returns all realms belonging to the authenticated builder, ordered by creation date (newest first).
Response
{ "success": true, "data": { "realms": [ { "id": "6d25623e-597e-4815-8bfa-6911b38c2079", "builderId": "54248205-...", "name": "Development", "slug": "development", "type": "demo", "description": "My development environment", "createdAt": "2026-02-11T21:12:52.529Z", "updatedAt": "2026-02-11T21:12:52.529Z" } ], "total": 1 }}Get Realm
GET/api/v1/realms/:idJWT / API Key
Retrieve a specific realm by ID.
Path Parameters
idstringrequiredDelete Realm
DELETE/api/v1/realms/:idJWT / API Key
Permanently delete a realm. This cannot be undone.
Response
{ "success": true, "data": { "deleted": true }}Update Realm Settings
PATCH/api/v1/realms/:id/settingsJWT / API Key
Update settings for a realm. Settings are stored as a JSON object and can include configuration like a default builder fee for exchange orders, and white-label branding for payment pages.
Request Body
defaultBuilderFeeBpsnumber | nullnull to clear.brandingobject | nullnull to clear.branding.displayNamestringbranding.accentColorstring"#4F46E5"). Must be a 6-digit hex color with # prefix.branding.logoUrlstringbranding.hideExpiryTimerbooleantrue, the expiry countdown is hidden on the payment page.branding.environmentBannerTextstringResponse
{ "success": true, "data": { "id": "6d25623e-...", "builderId": "54248205-...", "name": "Development", "slug": "development", "type": "demo", "description": "My development environment", "settings": { "defaultBuilderFeeBps": 5, "branding": { "displayName": "Acme Pay", "accentColor": "#4F46E5", "logoUrl": "https://example.com/logo.png" } }, "createdAt": "2026-02-11T...", "updatedAt": "2026-02-22T..." }}API Key Endpoints
All API key endpoints require JWT authentication. API keys are org-scoped and work across all realms belonging to the organization. The target realm is selected at request time via the realmId parameter, not at key creation.
Create API Key
POST/api/v1/api-keysJWT
Generate a new API key. The raw key is included in the response and is never returned again.
Request Body
namestringrequiredResponse 201 Created
{ "success": true, "data": { "apiKey": { "id": "388ffe67-5d95-4fc3-b4c2-0719b2ed756f", "orgId": "org_a1b2c3d4...", "createdBy": "54248205-...", "name": "Backend Service", "keyPrefix": "78ae7276", "status": "active", "createdAt": "2026-02-11T21:12:52.529Z", "revokedAt": null, "updatedAt": "2026-02-11T21:12:52.529Z" }, "rawKey": "arca_78ae7276_178e3df83d9d51372ad0..." }}The rawKey is only included in the creation response. Store it immediately in a secure location. If lost, revoke the key and create a new one.
List API Keys
GET/api/v1/api-keysJWT
List all API keys for the authenticated user's organization.
Response
{ "success": true, "data": { "apiKeys": [ { "id": "388ffe67-...", "orgId": "org_a1b2c3d4...", "createdBy": "54248205-...", "name": "Backend Service", "keyPrefix": "78ae7276", "status": "active", "createdAt": "2026-02-11T21:12:52.529Z", "revokedAt": null, "updatedAt": "2026-02-11T21:12:52.529Z" } ], "total": 1 }}Revoke API Key
DELETE/api/v1/api-keys/:idJWT
Revoke an API key. The key immediately becomes unusable. This cannot be undone.
Path Parameters
idstringrequiredResponse
{ "success": true, "data": { "revoked": true }}Signing Key Endpoints
Signing keys enable non-repudiation for API operations. When a builder registers an Ed25519 public key, they can sign every API request, creating cryptographic proof that the builder authorized the operation. Signing keys are org-scoped.
All signing key endpoints require JWT authentication.
Register Signing Key
POST/api/v1/signing-keysJWT
Register a new Ed25519 public key. The first key registered for an org is activated immediately. Subsequent keys enter a 72-hour grace period before activation (practice realms skip the grace period).
Request Body
{ "name": "production-key-2026", "publicKey": "MCowBQYDK2VwAyEA..."}namestringrequiredpublicKeystringrequiredResponse
{ "success": true, "data": { "id": "sgk_a1b2c3d4...", "orgId": "org_a1b2c3d4...", "builderId": "54248205-...", "name": "production-key-2026", "fingerprint": "e3b0c44298fc1c14", "state": "active", "activatesAt": null, "createdAt": "2026-04-07T12:00:00Z", "revokedAt": null, "updatedAt": "2026-04-07T12:00:00Z" }}The fingerprint is the first 16 hex characters of the SHA-256 hash of the public key. Use it as the X-Arca-Key-Id header value when signing requests.
List Signing Keys
GET/api/v1/signing-keysJWT
List all signing keys for the authenticated user's organization.
Response
{ "success": true, "data": { "signingKeys": [ { "id": "sgk_a1b2c3d4...", "orgId": "org_a1b2c3d4...", "builderId": "54248205-...", "name": "production-key-2026", "fingerprint": "e3b0c44298fc1c14", "state": "active", "createdAt": "2026-04-07T12:00:00Z", "updatedAt": "2026-04-07T12:00:00Z" } ], "total": 1 }}Revoke Signing Key
DELETE/api/v1/signing-keys/:idJWT
Revoke an active signing key. The key immediately becomes unusable for signing new requests. Historical signatures remain verifiable.
Response
{ "success": true, "data": { "revoked": true, "id": "sgk_a1b2c3d4..." }}Cancel Pending Key
POST/api/v1/signing-keys/:id/cancelJWT
Cancel a signing key that is still in its grace period (state pending). Cannot cancel keys that are already active or revoked.
Response
{ "success": true, "data": { "cancelled": true, "id": "sgk_a1b2c3d4..." }}List Key Events
GET/api/v1/signing-keys/:id/eventsJWT
View the tamper-evident audit log for a signing key. Every lifecycle event (registration, activation, revocation) is recorded with a hash-chained event log.
Response
{ "success": true, "data": { "events": [ { "id": "ske_x1y2z3...", "keyId": "sgk_a1b2c3d4...", "eventType": "registered", "actor": "54248205-...", "prevHash": "", "eventHash": "a1b2c3d4e5f6...", "createdAt": "2026-04-07T12:00:00Z" } ], "total": 1 }}Signing API Requests
Once you have a registered and active signing key, sign requests by adding three headers:
| Header | Value |
|---|---|
X-Arca-Key-Id | The key fingerprint (returned at registration) |
X-Arca-Signature | Base64-encoded Ed25519 signature |
X-Arca-Timestamp | Unix timestamp (seconds) — must be within 5 minutes of server time |
Signing Protocol
The signature is computed over a signing input constructed as:
arca-operation-v1\n<canonical-json-body>\n<timestamp>Where canonical-json-body is the request body with keys sorted alphabetically (recursively) and no extraneous whitespace. For requests with no body (GET, DELETE), use {}.
Example: Signing a Transfer
import { sign } from '@noble/ed25519';
const body = JSON.stringify({ source: "/treasury", target: "/payroll", amount: "1000" });const timestamp = Math.floor(Date.now() / 1000).toString();
// Canonical JSON: sort keys alphabeticallyconst canonical = JSON.stringify( JSON.parse(body), Object.keys(JSON.parse(body)).sort());
const signingInput = `arca-operation-v1\n${canonical}\n${timestamp}`;const signature = await sign( new TextEncoder().encode(signingInput), privateKeyBytes);
const response = await fetch("https://api.arca.network/api/v1/...", { method: "POST", headers: { "Authorization": "Bearer arca_78ae7276_...", "Content-Type": "application/json", "X-Arca-Key-Id": fingerprint, "X-Arca-Signature": btoa(String.fromCharCode(...signature)), "X-Arca-Timestamp": timestamp, }, body,});Signed requests are optional in Phase 1. When present, the server verifies the signature and records it on the operation for audit purposes. Future phases will make signing mandatory for production realms.
Arca Object Endpoints
All explorer endpoints require authentication (JWT or API key). Objects are scoped to a realm and a builder.
Path Field Naming
Endpoints use two conventions for Arca paths: path when defining the path of a new resource (e.g., the object being created), and arcaPath (or sourceArcaPath / targetArcaPath) when referencing an existing object. The path field also serves as an operation idempotency key where noted.
Ensure Arca Object
POST/api/v1/objectsJWT / API Key
Ensure an Arca object exists at the given path. If one already exists with matching type and denomination, it is returned with 200 OK. Otherwise a new object is created and returned with 201 Created. Safe to call on every request — use this instead of separate create-then-get patterns.
When creating, object creation runs asynchronously via a background workflow, but the object ID is generated upfront and returned immediately. The accompanying create operation starts in pending state and transitions to completed once the workflow finishes (typically within milliseconds). When returning an existing object, the operation field is omitted from the response.
Idempotency
Idempotency is enforced at two levels. If operationPath is provided, the system first checks whether that operation already exists — if it does, the existing result is returned (retry safety). Second, if an active Arca object exists at path with matching type/denomination, it is returned as-is (only requires ReadObject permission). A type or denomination mismatch returns an error. Metadata is not compared on the idempotent return path — the existing metadata is preserved.
Request Body
realmIdstringrequiredpathstringrequired/treasury/usd-reserve). Must be unique within the realm for active objects. Path segments cannot contain whitespace or control characters. To generate unique paths dynamically, use the nonce API (e.g., prefix /wallets/ produces /wallets/1, /wallets/2, etc.).typestring"denominated" (default), "exchange", "deposit", "withdrawal", "escrow".denominationstring"USD", "BTC"). Relevant for denominated objects.metadatastringoperationPathstring":" and prefix /op/create{arcaPath} to generate this (e.g., /op/create/wallets/main:1). Recommended for production use, especially when an Arca may be deleted and recreated at the same path.Response 201 Created (new object)
{ "success": true, "data": { "object": { "id": "obj_01h2xcejqtf2nbrexx3vqjhp41", "realmId": "6d25623e-...", "path": "/treasury/usd-reserve", "type": "denominated", "denomination": "USD", "status": "active", "metadata": null, "labels": null }, "operation": { "id": "op_01h2xcejqtf2nbrexx3vqjhp42", "realmId": "6d25623e-...", "path": "/op/create/treasury/usd-reserve:1", "type": "create", "state": "pending", "sourceArcaPath": null, "targetArcaPath": "/treasury/usd-reserve", "actorType": "builder", "actorId": "usr_01h2...", "createdAt": "2026-02-11T21:12:52.529000Z", "updatedAt": "2026-02-11T21:12:52.529000Z" } }}Response 200 OK (existing object)
{ "success": true, "data": { "object": { "id": "obj_01h2xcejqtf2nbrexx3vqjhp41", "realmId": "6d25623e-...", "path": "/treasury/usd-reserve", "type": "denominated", "denomination": "USD", "status": "active", "metadata": null, "labels": null } }}The object id is stable and safe to store immediately. When creating, the operation transitions from pending to completed once the background workflow finishes. When returning an existing object, the operation field is omitted and the response status is 200 OK.
Important: The object is not queryable by other endpoints (fund-account, transfers, balances) until the create operation reaches completed. Attempting to fund an object before it is fully materialized will return a 404 OBJECT_NOT_FOUND error. Use the SDK's waitForOperation() or subscribe to the object.created event before proceeding.
The immediate create response may return empty strings for object.createdAt and object.updatedAt because the object row is persisted asynchronously. Once the object appears in GET /api/v1/objects or GET /api/v1/objects/browse, those timestamps are authoritative and safe to use for newest-first ordering across devices.
List Arca Objects
GET/api/v1/objectsJWT / API Key
List objects in a realm. Optionally filter by path prefix.
Persisted objects returned here always include populated createdAt and updatedAt fields. If a client needs deterministic recency ordering for sibling accounts, sort the returned objects by createdAt descending and use a stable tie-breaker such as id.
Query Parameters
realmIdstringrequiredprefixstring/treasury).includeDeletedbooleanfalse.includestringlabels to include labels.Response
{ "success": true, "data": { "objects": [ { "id": "...", "path": "/treasury/usd-reserve", ... } ], "total": 1 }}Browse Objects
GET/api/v1/objects/browseJWT / API Key
S3-style hierarchical browsing. Returns folders (virtual path prefixes) and objects at the given level.
Browse responses now also surface path-level isolation metadata. Use paths for richer explorer UIs, including empty isolation zones that exist before any object is created beneath them. The legacy folders array remains for simple folder-only consumers.
As with GET /api/v1/objects, persisted objects in the browse response always carry authoritative createdAt and updatedAt values. Use this response, not the immediate create response, when shared-data clients need to pick the newest sibling object after a reset.
Query Parameters
realmIdstringrequiredprefixstring"/".includeDeletedbooleanfalse.Response
{ "success": true, "data": { "folders": ["/users"], "paths": [ { "path": "/users/alice", "kind": "isolation_zone", "hasObjects": false, "isEmpty": true, "isolation": { "isBoundaryRoot": true, "isInsideBoundary": false, "boundaryRootPath": "/users/alice", "boundaryId": "bnd_..." } } ], "currentIsolation": { "isBoundaryRoot": false, "isInsideBoundary": true, "boundaryRootPath": "/users/alice", "boundaryId": "bnd_..." }, "objects": [ { "id": "...", "path": "/users/alice/main", "isolation": { "isBoundaryRoot": false, "isInsideBoundary": true, "boundaryRootPath": "/users/alice", "boundaryId": "bnd_..." } } ] }}Create Isolation Zone
POST/api/v1/isolation-zonesJWT / API Key
Declare a path as an isolation-zone root before creating any object beneath it. This is the path-level source of truth used by the Explorer to render boundary markers and empty isolation zones.
Creation is idempotent by realmId + path. If the zone already exists, the API returns the existing declaration. If any active or withdrawn object already occupies that path or a nested child path, the request is rejected.
Request Body
realmIdstringrequiredpathstringrequired/users/alice. Root / is not allowed because it is already the realm's default boundary.Response
{ "success": true, "data": { "id": "izn_...", "realmId": "rlm_...", "path": "/users/alice", "boundaryId": "bnd_...", "createdAt": "2026-04-05T12:00:00.000000Z", "updatedAt": "2026-04-05T12:00:00.000000Z", "isolation": { "isBoundaryRoot": true, "isInsideBoundary": false, "boundaryRootPath": "/users/alice", "boundaryId": "bnd_..." } }}Object Valuation
GET/api/v1/objects/valuationJWT / API Key
Get the valuation for a single Arca object. Uses the same computation path as the aggregate endpoint, ensuring perfect additivity between single-object and multi-object queries (Axiom 10: Observational Consistency).
realmIdstringrequiredpathstringrequired/strategies/alpha).{ "ok": true, "data": { "objectId": "obj_123...", "path": "/strategies/alpha", "type": "exchange", "denomination": null, "valueUsd": "5234.56", "balances": [{ "denomination": "USD", "amount": "4900", "price": null, "valueUsd": "4900" }], "reservedBalances": [], "positions": [ { "coin": "hl:BTC", "size": "0.005", "side": "LONG", "entryPx": "94000", "markPrice": "96912", "unrealizedPnl": "334.56" } ] }}Aggregate by Path
GET/api/v1/objects/aggregateJWT / API Key
Compute total equity in USD for all Arca objects under a path prefix, broken down by asset type (spot denominations and perp positions). Exchange objects use the canonical Model C (full notional) accounting: equity = cash + sum(position_qty × mid_price). Cash may be negative for leveraged positions.
Query Parameters
realmIdstringrequiredprefixstringrequired/users/u-123/). Trailing slash = prefix match (all children). No trailing slash = exact match (single object, e.g. /users/u-123/exchanges/hl1).Reserved balances (in-flight amounts tied to pending operations) are included in totalEquityUsd since they represent value the user still owns. The departingUsd field breaks out how much of the total is currently departing, and arrivingUsd shows how much is in transit toward objects within the prefix.
Response
{ "success": true, "data": { "prefix": "/users/u-123/", "totalEquityUsd": "15734.56", "departingUsd": "500", "arrivingUsd": "200", "breakdown": [ { "asset": "USD", "category": "spot", "amount": "10000", "price": null, "valueUsd": "10000" }, { "asset": "USD-EXCHANGE", "category": "exchange", "amount": "4900", "price": null, "valueUsd": "4900" }, { "asset": "BTC", "category": "spot", "amount": "0.05", "price": "98000", "valueUsd": "4900" }, { "asset": "ETH", "category": "perp", "amount": "1.5", "price": "2800", "valueUsd": "334.56", "weightedAvgLeverage": "3.00" } ], "objects": [ { "objectId": "a1b2...", "path": "/users/u-123/usd", "type": "denominated", "denomination": "USD", "valueUsd": "10500", "balances": [{ "denomination": "USD", "amount": "10000", "price": null, "valueUsd": "10000" }], "reservedBalances": [ { "denomination": "USD", "amount": "500", "price": null, "valueUsd": "500", "operationId": "op-1234..." } ], "positions": null }, { "objectId": "c3d4...", "path": "/users/u-123/exchange", "type": "exchange", "denomination": null, "valueUsd": "5234.56", "balances": [{ "denomination": "USD", "amount": "4900", "price": null, "valueUsd": "4900" }], "reservedBalances": [], "positions": [ { "coin": "ETH", "side": "LONG", "size": "1.5", "entryPrice": "2750", "markPrice": "2800", "unrealizedPnl": "75", "valueUsd": "334.56" } ] } ] }}Aggregation History
GET/api/v1/objects/aggregate/historyJWT / API Key
Get historical equity time-series for all objects under a path prefix. Returns equity values sampled evenly over the time range.
Query Parameters
realmIdstringrequiredprefixstringrequiredfromstringrequiredtostringrequiredpointsnumberP&L
GET/api/v1/objects/pnlJWT / API Key
Get profit and loss for objects under a path prefix over a time range. Returns starting/ending equity, net inflows/outflows, and calculated P&L.
Query Parameters
realmIdstringrequiredprefixstringrequiredfromstringrequiredtostringrequiredP&L History
GET/api/v1/objects/pnl/historyJWT / API Key
Get a time-series of P&L and equity for objects under a path prefix over a time range. Returns sampled P&L points and external flows (deposits, withdrawals, transfers).
Query Parameters
realmIdstringrequiredprefixstringrequiredfromstringrequiredtostringrequiredpointsnumberResponse
{ "success": true, "data": { "prefix": "/", "from": "2026-03-01T00:00:00.000000Z", "to": "2026-03-10T00:00:00.000000Z", "points": 200, "startingEquityUsd": "10000.00", "pnlPoints": [ { "timestamp": "2026-03-01T00:00:00.000000Z", "pnlUsd": "0.00", "equityUsd": "10000.00" }, { "timestamp": "2026-03-02T12:00:00.000000Z", "pnlUsd": "150.32", "equityUsd": "10150.32" } ], "externalFlows": [ { "operationId": "op-1234...", "type": "deposit", "direction": "inflow", "amount": "500", "denomination": "USD", "valueUsd": "500.00", "sourceArcaPath": null, "targetArcaPath": "/wallets/main", "timestamp": "2026-03-05T14:00:00.000000Z" } ] }}Aggregation Watches
POST/api/v1/aggregations/watchJWT / API Key
Watches provide real-time, targeted aggregation updates. Create a watch to subscribe to aggregated equity for a set of objects defined by prefix, glob pattern, explicit path list, or composition of other watches. The server pushes aggregation.updated WebSocket events only when an object in the watch changes, enabling efficient targeted invalidation instead of polling.
Create Watch
/api/v1/aggregations/watchJWT / API KeyRequest Body
{ "realmId": "realm-123", "sources": [ { "type": "prefix", "value": "/users/u-123/" }, { "type": "pattern", "value": "/users/*/exchanges/hl/*" }, { "type": "paths", "value": "/treasury/usd,/treasury/btc" }, { "type": "watch", "value": "<other-watch-id>" } ]}realmIdstringrequiredsourcesarrayrequiredtype (prefix, pattern, paths, or watch) and a value string.paths accepts comma-separated Arca paths.pattern supports * as a single-segment wildcard. watch composes another watch by ID.Response
{ "success": true, "data": { "watchId": "a1b2c3d4-...", "aggregation": { "prefix": "(watch)", "totalEquityUsd": "15734.56", "departingUsd": "500", "breakdown": [...], "objects": [...] } }}Get Watch Aggregation
/api/v1/aggregations/watch/:watchIdJWT / API KeyReturns the current aggregation for a watch (recomputed from Redis object leaves). The response shape is the same as the aggregate-by-path endpoint.
Destroy Watch
/api/v1/aggregations/watch/:watchIdJWT / API KeyRemoves the watch subscription. Watches are also automatically evicted after 5 minutes of inactivity.
Get Object Detail
GET/api/v1/objects/:idJWT / API Key
Retrieve an Arca object with its full history — operations, events, state deltas, and current balances.
Path Parameters
idstringrequiredResponse
{ "success": true, "data": { "object": { "id": "a1b2c3d4-...", "realmId": "6d25623e-...", "path": "/treasury/usd-reserve", "type": "denominated", "denomination": "USD", "status": "active", "metadata": null, "labels": null, "createdAt": "2026-02-11T21:12:52.529Z", "updatedAt": "2026-02-11T21:12:52.529Z" }, "operations": [ { "id": "...", "type": "create", ... } ], "events": [ { "id": "...", "type": "deposit.completed", ... } ], "deltas": [ { "id": "...", "deltaType": "balance_change", ... } ], "balances": [ { "id": "...", "denomination": "USD", "amount": "1000.00" } ] }}Get Object Balances
GET/api/v1/objects/:id/balancesJWT / API Key
Retrieve all current balances for a specific Arca object.
Path Parameters
idstringrequiredResponse
{ "success": true, "data": { "balances": [ { "denomination": "USD", "arriving": "200.00", "settled": "800.00", "departing": "0.00", "total": "1000.00" } ] }}Get Object Snapshot (As-Of)
GET/api/v1/objects/:id/snapshotJWT / API Key
Retrieve historical balances and canonical perp positions for an object at a specific timestamp using append-only ledger rows.
Path Parameters
idstringrequiredQuery Parameters
realmIdstringrequiredasOfstringrequiredResponse
{ "success": true, "data": { "realmId": "r1", "arcaId": "a1", "asOf": "2026-02-18T18:00:00Z", "balances": [{ "denomination": "USD", "amount": "1200.00" }], "positions": [{ "market": "BTC", "side": "LONG", "size": "0.25", "leverage": 3 }] }}Deletion Readiness
GET/api/v1/objects/:id/deletion-readinessJWT / API Key
Pre-flight check before deleting an Arca object. Returns whether the object can be deleted, and if not, the reasons blocking deletion (e.g., in-flight operations, non-zero balances without a sweep target, open exchange positions).
Query Parameters
realmIdstringrequiredDelete Arca Object
POST/api/v1/objects/deleteJWT / API Key
Delete an Arca object. If the object has remaining balances, provide a sweepToPath to transfer funds to another Arca before deletion. The delete executes as a Temporal workflow: first setting status to deleting (blocking all operations), then performing any necessary liquidation, fund withdrawal, sweep, and finalizing to deleted.
In-flight blocking: If the object has any in-flight operations (outbound or inbound holds in held status), the delete is rejected. Wait for pending operations to reach a terminal state before requesting deletion.
Exchange objects: For exchange-type Arca objects with open positions, set liquidatePositions: true to close all positions via market order before deletion. The resulting cash is automatically withdrawn to the platform balance and swept to the target. The workflow has a 5-minute timeout; on timeout, the object reverts to active status.
Request Body
realmIdstringrequiredobjectIdstringobjectId or path.pathstringobjectId.sweepToPathstringliquidatePositionsbooleantrue to liquidate all open exchange positions via market orders before deletion. Required for exchange objects with open positions. Defaults to false.Response 200 OK
{ "success": true, "data": { "object": { "id": "a1b2c3d4-...", "realmId": "6d25623e-...", "path": "/treasury/usd-reserve", "displayName": "usd-reserve.deleted.2026.02.12.37800", "type": "denominated", "denomination": "USD", "status": "deleted", "deletedAt": "2026-02-12T10:30:00.000Z", "createdAt": "2026-02-11T21:12:52.529Z", "updatedAt": "2026-02-12T10:30:00.000Z" }, "operation": { "id": "e5f6a7b8-...", "type": "delete", "state": "completed", "sourceArcaPath": "/treasury/usd-reserve", "targetArcaPath": "/treasury/sweep-account" } }}Get Object Versions
GET/api/v1/objects/:id/versionsJWT / API Key
Retrieve all versions of an Arca object at the same path. When an Arca is deleted, a deletedAt timestamp is set and the path becomes available for reuse. If a new Arca is later created at the same path and also deleted, each becomes a separate version. Deleted versions also get a tombstone displayNameso explorer-style surfaces can distinguish old generations without changing the canonical path. When an Arca is fully withdrawn, its status becomes withdrawn, withdrawnAt is set, and the path remains permanently retired. This endpoint returns all generations sorted newest-first.
Path Parameters
idstringrequiredResponse
{ "success": true, "data": { "versions": [ { "id": "...", "path": "/treasury/usd-reserve", "displayName": null, "status": "active", "deletedAt": null, "withdrawnAt": null, ... }, { "id": "...", "path": "/treasury/usd-reserve", "displayName": "usd-reserve.deleted.2026.02.10.54000", "status": "deleted", "deletedAt": "2026-02-10T15:00:00.000Z", "withdrawnAt": null, ... }, { "id": "...", "path": "/treasury/retired-wallet", "displayName": null, "status": "withdrawn", "deletedAt": null, "withdrawnAt": "2026-04-05T15:00:00.000Z", ... } ] }}Update Labels
PATCH/api/v1/objects/:id/labelsJWT / API Key
Update labels on an Arca object. Uses merge semantics: keys with string values are set (or overwritten), keys with null values are removed. Existing labels not mentioned in the request body are left unchanged.
Requires the arca:UpdateObject permission on the object path.
Path Parameters
idstringrequiredQuery Parameters
realmIdstringrequiredRequest Body
{ "labels": { "displayName": "Main Wallet", "tier": "gold", "removeMe": null }}labelsRecord<string, string | null>requirednull to remove it.Constraints
- Max 32 keys per object.
- Keys: 1–63 characters, must start with a letter (a-z/A-Z), then a-z, A-Z, 0-9,
.,_,-. - Values: max 256 characters.
- Total label payload: max 4KB.
- Keys prefixed with
arca.or_are reserved and cannot be set by builders.
Response 200 OK
{ "success": true, "data": { "object": { "id": "obj_01h2xcejqtf2nbrexx3vqjhp41", "realmId": "6d25623e-...", "path": "/wallets/main", "type": "denominated", "denomination": "USD", "status": "active", "metadata": null, "labels": { "displayName": "Main Wallet", "tier": "gold" } } }}ArcaObject Response Shape
All endpoints that return an Arca object include the following fields. The labels field is null by default on list responses unless ?include=labels is passed, and is always populated on single-object responses (get, create, update).
idstringrealmIdstringpathstringdisplayNamestring | null wallet.deleted.YYYY.MM.DD.SSSSS so repeated generations at the same path stay distinguishable while the canonical path remains unchanged.typestringdenominated, exchange, deposit, withdrawal, escrow.denominationstring | nullstatusstringactive, deleting, deleted, withdrawn.deletedAtstring | nullwithdrawnAtstring | nullmetadatastring | nulllabelsRecord<string, string> | nullnull when not loaded (list without ?include=labels) or when no labels have been set.createdAtstringupdatedAtstringLabel Use Cases
Labels are free-form key-value pairs that let you attach application-specific metadata to Arca objects without a separate data store. Here are common patterns:
Display names
Store human-readable names on wallets so listObjects returns everything the UI needs in a single call.
await arca.updateLabels(wallet.id, { displayName: 'Savings Account' });
const { objects } = await arca.listObjects({ prefix: '/wallets', include: 'labels' });for (const obj of objects) { console.log(obj.labels?.displayName ?? obj.path);}External system mapping
Store external IDs like stripeCustomerId to avoid maintaining a join table between your system and Arca.
await arca.updateLabels(wallet.id, { stripeCustomerId: 'cus_abc123', internalUserId: 'u-789',});Categorization
Tag objects by purpose or region for client-side grouping and filtering.
await arca.updateLabels(wallet.id, { region: 'us-east', purpose: 'payroll',});
const { objects } = await arca.listObjects({ include: 'labels' });const payrollWallets = objects.filter(o => o.labels?.purpose === 'payroll');Onboarding state
Track onboarding progress and tier directly on objects instead of a separate user table.
await arca.updateLabels(wallet.id, { onboardingStep: 'kyc-complete', tier: 'gold',});Ops / support annotations
Flag objects with a support ticket reference for investigation without touching balances or operations.
await arca.updateLabels(wallet.id, { supportTicket: 'TICKET-4521', flaggedReason: 'suspicious-activity',});
// Clear the flag when resolvedawait arca.updateLabels(wallet.id, { supportTicket: null, flaggedReason: null,});Operation Endpoints
Operations are created automatically by high-level endpoints (transfer, fund-account, defund-account, object creation/deletion, order placement). You do not need to create them directly — use the endpoints below to query existing operations.
When an operation fails, the response includes a failureMessage field with a human-readable description of the failure that is safe to display to end users. The outcome field continues to carry the full structured JSON for programmatic inspection.
List Operations
GET/api/v1/operationsJWT / API Key
List operations in a realm. Optionally filter by type.
Query Parameters
realmIdstringrequiredtypestring"deposit").typesstring"fill,transfer"). Takes precedence over type.includeContextstring"true", each operation includes its typed context inline (transfer amount/fee, fill details, etc.).Response
{ "success": true, "data": { "operations": [ { "id": "...", "type": "deposit", "state": "completed", ... } ], "total": 5 }}Get Operation Detail
GET/api/v1/operations/:idJWT / API Key
Retrieve an operation with its correlated events and state deltas. This is the primary view for understanding what an operation did.
The response includes a typed context object with structured data for the operation type (fill, transfer, deposit, etc.), so you don't need to parse raw event payloads. The parsedOutcome field on the operation provides the outcome as a parsed object instead of a JSON string. Deltas from internal system objects are flagged with "internal": true.
Path Parameters
idstringrequiredQuery Parameters
includeEvidencestring"true", includes a Phase 1 audit evidence bundle with journal rows, proof references, chain transaction metadata, and integrity notes.Response — Fill (Trade Execution)
{ "success": true, "data": { "operation": { "id": "op_abc...", "type": "fill", "state": "completed", "parsedOutcome": { "status": "completed", "type": "fill" }, ... }, "context": { "type": "fill", "fill": { "coin": "hl:BTC", "side": "SELL", "size": "0.01", "price": "73448", "market": "hl:BTC", "dir": "Reduce Long", "orderId": "ord_abc...", "orderOperationId": "op_xyz...", "realizedPnl": "39.92", "fee": "0.40", "feeBreakdown": { "exchange": "0.07", "platform": "0.33", "builder": "0.00" }, "netBalanceChange": "39.52", "startPosition": "0.02", "resultingPosition": { "side": "LONG", "size": "0.01", "entryPx": "69000", "leverage": 5 }, "isLiquidation": false } }, "events": [ ... ], "deltas": [ { "deltaType": "balance_change", "arcaPath": "/exchanges/main", "internal": false, ... }, { "deltaType": "balance_change", "arcaPath": "/_system/costs/exchange/...", "internal": true, ... } ] }}Response — Transfer
{ "success": true, "data": { "operation": { "type": "transfer", "state": "completed", ... }, "context": { "type": "transfer", "transfer": { "amount": "500", "denomination": "USD", "sourceArcaPath": "/wallets/main", "targetArcaPath": "/exchanges/main" } }, "events": [ ... ], "deltas": [ ... ] }}Response — Evidence Enabled
{ "success": true, "data": { "operation": { "id": "op_abc...", "type": "deposit", "state": "completed", ... }, "events": [ ... ], "deltas": [ ... ], "evidence": { "version": "audit-evidence.v1", "journal": { "entries": [ { "id": "je_123", "realmId": "realm_abc", "operationId": "op_abc...", "opType": "deposit", "authority": "authoritative", "status": "committed", "createdAt": "2026-04-05T16:00:00.000000Z", "committedAt": "2026-04-05T16:00:00.000000Z" } ], "legs": [ { "entryId": "je_123", "id": "jl_1", "accountId": "acct_main", "denomination": "USD", "amount": "100", "kind": "credit" } ], "proofs": [ { "entryId": "je_123", "id": "jp_1", "proofType": "chain_tx", "externalRef": "0xabc123..." } ], "chainTransactions": [ { "id": "ctx_1", "realmId": "realm_abc", "operationId": "op_abc...", "txHash": "0xabc123...", "chain": "arbitrum", "direction": "inbound", "status": "submitted", "confirmations": 0, "createdAt": "2026-04-05T16:00:00.000000Z", "updatedAt": "2026-04-05T16:00:00.000000Z" } ] }, "integrity": { "appendOnlyHistory": true, "proofUniqueness": { "enforced": true, "mechanism": "journal_proofs_by_ref_unique" }, "runtimeChecks": [ { "key": "correlationSpine", "label": "Correlation Spine", "description": "Operation, events, and state deltas are linked through append-only history." } ], "cryptographicSequence": { "present": false, "note": "Phase 1 exposes correlated evidence, not a cryptographic hash chain." } } } }}Context Types
The context.type discriminator tells you which nested object is populated:fill, transfer, deposit, withdrawal,order (placement), cancel, or delete.
What this does not prove: Phase 1 is not yet a cryptographic hash chain, Merkle proof, or proof-of-state system. The response will explicitly report
cryptographicSequence.present = false.Export Operation Evidence
GET/api/v1/operations/exportJWT / API Key
Export realm-scoped operation history for a time range. The export always includes the same evidence block used by GET /api/v1/operations/:id?includeEvidence=true.
Query Parameters
realmIdstringrequiredfromstringrequiredtostringrequiredtypestringtypesstringtype.arcaPathstringpathstringlimitinteger100; maximum 500.cursorstringnextCursor from the previous page. Pass it back verbatim to fetch the next page.Response
{ "success": true, "data": { "version": "audit-evidence.v1", "realmId": "realm_abc", "from": "2026-04-01T00:00:00.000000Z", "to": "2026-04-05T23:59:59.000000Z", "exportedAt": "2026-04-05T18:45:00.000000Z", "nextCursor": "100", "operations": [ { "operation": { "id": "op_abc...", "type": "transfer", "state": "completed", ... }, "context": { ... }, "events": [ ... ], "deltas": [ ... ], "evidence": { ... } } ] }}Event Endpoints
Events are created automatically as operations execute. Use these endpoints to query existing events.
List Events
GET/api/v1/eventsJWT / API Key
List events in a realm.
Query Parameters
realmIdstringrequiredResponse
{ "success": true, "data": { "events": [ { "id": "...", "type": "deposit.completed", ... } ], "total": 12 }}Get Event Detail
GET/api/v1/events/:idJWT / API Key
Retrieve an event with its correlated operation and state deltas.
Path Parameters
idstringrequiredResponse
{ "success": true, "data": { "event": { "id": "...", "type": "deposit.completed", ... }, "operation": { "id": "...", "type": "deposit", "state": "completed", ... }, "deltas": [ { "id": "...", "deltaType": "balance_change", ... } ] }}Fund Account Endpoints
These endpoints are developer tools for testing, competitions, and programmatic account seeding. For production deposit and withdrawal flows, use Payment Links.
Fund Account
POST/api/v1/fund-accountJWT / API Key
Fund an Arca object (denominated or exchange). This creates a pending deposit operation and triggers settlement. On completion, a deposit.completed event is emitted with the balance_change delta. For exchange objects, the deposit is forwarded to the venue after on-chain confirmation.
Settlement by Realm Type
- demo / development / testing — auto-mints tokens to the realm's custody pool and settles automatically within seconds. No real tokens needed.
- production — creates a 30-minute deposit intent. The response includes
poolAddressandtokenAddress. The operation stays pending until real tokens arrive on-chain. If no tokens are received, the operation remains pending indefinitely.
Request Body
realmIdstringrequiredarcaPathstringrequiredamountstringrequired"1000.00").pathstringResponse 201 Created
{ "success": true, "data": { "operation": { "id": "e5f6a7b8-...", "type": "deposit", "state": "pending", ... }, "event": { "id": "f1e2d3c4-...", "type": "deposit.completed", "payload": "{\"amount\":\"1000.00\",\"arcaPath\":\"/treasury/usd-reserve\"}", ... } }}On-Chain Deposits (Real Money)
When on-chain custody is active, the fund-account response also includes pool address information. The user sends tokens (USDC) directly to the pool address on-chain. The platform detects the transfer via a background ChainMonitor and automatically credits the Arca object after sufficient block confirmations.
The credited amount is determined by what arrives on-chain, not by what the caller declares. The amount field in the request is advisory only — the actual credited amount equals the on-chain transfer amount.
On-Chain Response Fields
poolAddressstringtokenAddressstringchainstring"reth" for demo, "arbitrum" for production).expiresAtstringOn-Chain Funding Flow
- Call
POST /fund-accountto create a funding intent - Response includes
poolAddress— send USDC to this address on-chain - The platform monitors the chain for incoming transfers
- When the transfer is detected, a confirmation workflow waits for N block confirmations
- After confirmation, the Arca object is credited with the actual on-chain amount
- A
deposit.confirmedevent is emitted
Demo Realms
In demo realms, on-chain funding can be simulated through the portal Fund Account modal (Simulate On-Chain Sendbutton) without requiring a real wallet or testnet tokens.
Defund Account Endpoints
These endpoints are developer tools for testing, competitions, and programmatic account management. For production withdrawal flows, use Payment Links.
Defund Account
POST/api/v1/defund-accountJWT / API Key
Withdraw funds from an Arca object to an external on-chain address. When on-chain custody is active, the funds are sent to the specified destination address. In demo realms, the defunding is simulated. For exchange objects, first transfer funds to a denominated object, then defund from there.
Request Body
realmIdstringrequiredarcaPathstringrequiredamountstringrequireddestinationAddressstringpathstringResponse 201 Created
{ "success": true, "data": { "operation": { "id": "e5f6a7b8-...", "type": "withdrawal", "state": "pending", ... }, "txHash": "0xabc123..." }}Custody Endpoints
Get Custody Status
GET/api/v1/custody/statusJWT / API Key
Returns the full custody contract state for a realm, including all isolation boundaries, exchange arcas, and venue halt status. When no custody contract is configured, returns configured: false.
Query Parameters
realmIdstringrequiredResponse
{ "configured": true, "contractAddress": "0x...", "chainId": 31337, "totalBalance": "12500000", "boundaries": [ { "id": "bnd_...", "realmId": "rlm_...", "balance": "10000000", "status": "active", "lockedBy": null, "recoveryKey": null, "agentAddress": null, "timeLock": 0, "pendingExit": null } ], "exchangeArcas": [ { "id": "obj_...", "boundaryId": "bnd_...", "status": "Active", "balance": "2500000", "vaultAddress": "" } ], "venueHalt": null}List Boundaries
GET/api/v1/custody/boundariesJWT / API Key
List all isolation boundaries for a realm.
Query Parameters
realmIdstringrequiredResponse
{ "boundaries": [ { "id": "bnd_...", "realmId": "rlm_...", "balance": "10000000", "status": "active", "lockedBy": null, "recoveryKey": null, "agentAddress": null, "timeLock": 0, "pendingExit": null } ]}Get Boundary
GET/api/v1/custody/boundaries/{boundaryId}JWT / API Key
Returns a single isolation boundary by ID.
Query Parameters
realmIdstringrequiredList Exchange Arcas
GET/api/v1/custody/exchange-arcasJWT / API Key
List all exchange arcas across boundaries, optionally filtered by a specific boundary ID.
Query Parameters
realmIdstringrequiredboundaryIdstringResponse
{ "exchangeArcas": [ { "id": "obj_...", "boundaryId": "bnd_...", "status": "Active", "balance": "2500000", "vaultAddress": "" } ]}Register Recovery Key
POST/api/v1/custody/recovery-keyJWT / API Key
Register a recovery key for an isolation boundary. The recovery key holder gains the ability to lock the boundary, which freezes all operator fund movement. This is a one-way operation — once set, the recovery key can only be changed by the current recovery key holder.
Request Body
realmIdstringrequiredboundaryIdstringrequiredwalletAddressstringrequiredResponse
{ "boundaryId": "bnd_...", "recoveryKey": "0x..."}Transfer Endpoints
Execute Transfer
POST/api/v1/transferJWT / API Key
Transfer balance between two Arca objects. Both objects must share the same denomination and the source must have sufficient funds. For denominated-to-denominated transfers, the operation is atomic (single Spanner transaction). For transfers involving an exchange object, the operation is async — funds pass through an on-chain phase and the operation remains pending until settlement completes. Exchange-to-exchange transfers are not supported; route through a denominated object instead.
Request Body
realmIdstringrequiredpathstringrequired"/op/transfer/alice-to-bob-1"). Must be unique per realm. If a transfer already exists at this path, the existing result is returned (idempotent).sourceArcaPathstringrequired"/alice/wallet").targetArcaPathstringrequired"/bob/wallet").amountstringrequired"250.00").Idempotency
The path field serves as an idempotency key. If a transfer operation already exists at the given path within the realm, the endpoint returns the existing operation and event without executing a second transfer. The path is enforced as unique per realm by a database index.
Response 201 Created
{ "success": true, "data": { "operation": { "id": "a1b2c3d4-...", "path": "/op/transfer/alice-to-bob-1", "type": "transfer", "state": "completed", "sourceArcaPath": "/alice/wallet", "targetArcaPath": "/bob/wallet", "input": "{\"amount\":\"250.00\",\"denomination\":\"USD\",\"sourceArcaPath\":\"/alice/wallet\",\"targetArcaPath\":\"/bob/wallet\"}", "outcome": "{\"status\":\"completed\",\"amount\":\"250.00\",\"denomination\":\"USD\",\"sourceNewBalance\":\"750.00\",\"targetNewBalance\":\"1250.00\"}", ... } }}The operation's outcome field contains the transfer result including the new balances for both source and target. No separate event is created — the operation reaching "completed" state is the terminal signal.
Atomicity Guarantees
The transfer executes inside a single Spanner read-write transaction. If any step fails (insufficient balance, object not found, denomination mismatch), the entire transaction aborts with no side-effects — no partial debits, no orphaned operations.
Error Cases
400VALIDATION_ERROR404NOT_FOUNDExample: curl
curl -X POST http://localhost:3052/api/v1/transfer \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "realmId": "your-realm-id", "path": "/op/transfer/fund-exchange-1", "sourceArcaPath": "/wallets/main", "targetArcaPath": "/exchanges/btc-trader", "amount": "500.00" }'Transfer vs Defund Account
Transfer moves value between two Arca objects within a realm — this includes funding and defunding exchange accounts (e.g., moving USD from a wallet to an exchange Arca, or vice versa). Defund Account (POST /api/v1/defund-account) moves value off-platform to an external on-chain address. Use transfer for internal movement; use defund-account only when funds leave the platform. For production withdrawal flows, use Payment Links.
Exchange Arca
Exchange Arca objects represent simulated perpetual futures exchange accounts. They are currently supported in demo realms only and use the Hyperliquid exchange simulation backend.
Creating an Exchange Arca
Create an exchange Arca using the standard object creation endpoint withtype: "exchange" and metadata containing exchangeType. The denomination is automatically set to USD.
POST /api/v1/objects{ "realmId": "...", "path": "/exchanges/my-hl", "type": "exchange", "denomination": "USD", "metadata": "{\"exchangeType\": \"hyperliquid\"}"}The system automatically creates a sim-exchange account and stores the simAccountId in the object's metadata.
Balance Model
Every balance exposes four fields that describe where value is during a transfer lifecycle:
| Field | Meaning | Non-zero when |
|---|---|---|
settled | Value available for new operations | Object holds funds that are not currently in transit |
departing | Value committed to leave via an outbound transfer | An outbound transfer is pending (hold placed, not yet settled) |
arriving | Value in transit toward this object, past the point of no return at the source | An inbound transfer to an exchange object is pending (venue has not yet confirmed receipt) |
total | arriving + settled + departing | Always (the complete value attributed to the object) |
Denominated → denominated transfers settle atomically — the source is debited and destination credited in a single transaction. There is no arriving or departing phase; both remain 0.
Exchange (venue) transfers have an in-flight phase. After the source is debited, the amount appears as departing at the source (or arriving at the exchange destination) until the venue confirms receipt. This prevents the exchange from treating funds as available before they physically arrive.
Presenting In-Flight Funds
Builders have full flexibility in how they surface these states to end users:
- Simple — show only
total. Users see one number with no intermediate states. - Detailed — show
settled,arriving, anddepartingseparately with labels (e.g. "Available", "Incoming", "Outgoing"). - Notification-based — show
totalas the primary balance, but display an inline indicator or toast whenarrivingordepartingis non-zero (e.g. "$200 arriving").
Transfer Protocol
All transfers use the same POST /api/v1/transfer endpoint regardless of source and target types. The backend resolves sender and receiver behaviors based on the object types involved:
- Immediate settlement (e.g. denominated → denominated): Both sides settle atomically in a single transaction. The operation is created with state
completed. - Async settlement (e.g. denominated → exchange): Funds are debited from the source and placed into a reserved balance. The operation starts as
pendingand a background workflow delivers the funds to the receiver when it is ready.
To transfer into an exchange, simply use arca:ReceiveTo on the exchange path. There is no separate "fund exchange" permission or operation type. Reserved balances appear in the object detail response while the transfer is in flight.
Reserved Balances
The reservedBalances field on the object detail response shows in-flight amounts:
{ "id": "...", "arcaId": "...", "operationId": "...", "denomination": "USD", "amount": "1000.00", "status": "held", "direction": "outbound", "sourceArcaPath": "/wallets/main", "destinationArcaPath": "/exchanges/hl1", "createdAt": "...", "updatedAt": "..."}direction: outbound means funds are leaving this object; inbound means funds are arriving. Status values: held (in-flight), released (settled), cancelled (operation failed).
Fee Estimation
GET /api/v1/fees/estimate?realmId=...&action=transfer&amount=1000&sourcePath=/wallets/main&targetPath=/exchange/mainEstimate the fee for a transfer or order before executing it. Returns the fee amount, denomination, and net amount after fee deduction.
realmIdstringrequiredactionstringrequiredtransfer or order.amountstringrequiredsourcePathstringtargetPathstring{ "success": true, "data": { "fee": { "amount": "0.05", "denomination": "USD" }, "netAmount": "999.95" }}Exchange Account State
GET /api/v1/objects/{id}/exchange/stateReturns account state including margin summary, positions, and open orders. The marginSummary object is the authoritative source for exchange account balances — there is no separate usdBalance field exposed in the API. To determine max order sizes, use the Active Asset Data endpoint below instead of computing from balance fields.
marginSummary Fields
equitystringtotalRawUsdstringavailableToWithdrawstringequity - initialMarginUsed or the active asset data endpoint for trading capacity.initialMarginUsedstringmaintenanceMarginRequiredstringtotalUnrealizedPnlstringtotalNtlPosstringActive Asset Data (Max Order Size)
GET /api/v1/objects/{id}/exchange/active-asset-data?coin=BTCReturns server-computed max trade sizes for a specific coin on an exchange object. This is a one-shot snapshot — for live updates that react to margin, position, and price changes, use the SDK's watchMaxOrderSize() method instead. The server accounts for available margin, leverage, fees, existing positions, and current order book prices.
Response Fields
coinstringleverageobject{type, value}. type is "cross" or "isolated".maxBuySizestringmaxSellSizestringmaxBuyUsdstringmaxSellUsdstringavailableToTradestringmarkPxstringfeeRatestringAsset Fees
GET /api/v1/objects/{id}/exchange/asset-feesGET /api/v1/objects/{id}/exchange/asset-fees?builderFeeBps=40Returns the effective taker and maker fee rates for every tradeable asset on the given exchange account. Rates are fully composed: exchange volume-tier base rate scaled by the per-asset HIP-3 fee multiplier, plus platform fee, plus optional builder fee. The optional builderFeeBps query parameter adds a builder fee component (in tenths of a basis point).
Response Fields (array)
coinstring"hl:BTC").takerFeeRatestring"0.00045" = 4.5 bps).makerFeeRatestring"0.00015" = 1.5 bps).Update Leverage
POST /api/v1/objects/{id}/exchange/leverage{ "coin": "hl:BTC", "leverage": 10}Set the leverage for a coin. The coin field expects the canonical market ID (e.g., "hl:BTC"). Leverage is a per-coin setting (not per-order), matching Hyperliquid's model. Changing leverage re-margins any existing position at the new leverage. If the account can't afford the increased margin (when decreasing leverage), the request is rejected. Increasing leverage always succeeds as it releases margin. Defaults to 1x if not set.
Get Leverage
GET /api/v1/objects/{id}/exchange/leverage # All coinsGET /api/v1/objects/{id}/exchange/leverage?coin=hl:BTC # Single coinPlace Order (Operation)
POST /api/v1/objects/{id}/exchange/orders{ "realmId": "...", "path": "/op/order/btc-buy-1", "coin": "hl:BTC", "side": "BUY", "orderType": "MARKET", "size": "0.001", "leverage": 5}Trigger order (TP/SL) example — same endpoint, optional trigger fields:
{ "realmId": "...", "path": "/op/order/btc-tp-1", "coin": "hl:BTC", "side": "SELL", "orderType": "LIMIT", "size": "0.001", "price": "100000", "isTrigger": true, "triggerPx": "99000", "isMarket": false, "tpsl": "tp", "grouping": "normalTpsl"}Placing an order is now a full operation with idempotency. The path field is the idempotency key — same path with same inputs returns the prior result. The response contains the operation record.
When leverage is provided, the account's per-coin leverage is automatically set before placing the order (equivalent to calling Update Leverage then Place Order). If omitted, the order uses whatever leverage is currently set for the coin (default 1x).
Supported fields: coin (canonical market ID, e.g. "hl:BTC"), side (BUY/SELL),orderType (MARKET/LIMIT), size, price(required for LIMIT), leverage (sets per-coin leverage before order),reduceOnly,timeInForce (GTC/IOC/ALO),builderFeeBps (tenths of a basis point, e.g. 45 = 4.5 bps — additive to exchange and platform fees; see System-Owned Objects for fee routing).
Trigger orders (TP/SL): optional fields isTrigger (boolean),triggerPx (string — mark price threshold to activate),isMarket (boolean — if true, execute as market when triggered; if false, use price as the limit),tpsl ("tp" or "sl"), and grouping ("na", "normalTpsl", or "positionTpsl"). Standalone triggers use grouping: "na". normalTpsl is fixed-size and parent-linked (OCO-style: when one sibling triggers, siblings cancel). positionTpsl auto-resizes with the position and cancels when the position closes.
Max size orders: set useMax: true to have the server resolve the actual max order size at execution time. The size field serves as a reference (what was displayed to the user). The server will never fill more than the reference, and rejects the order if the resolved max drops below the reference by more than sizeTolerance (default 2%, max 25%). This eliminates race conditions between client-side max computation and server-side margin checks.
Size tolerance: sizeTolerance works on any order, not just useMax. When an order fails a margin check (e.g. due to price drift between client computation and server execution), the server will reduce the order size by up to the tolerance percentage. It only reduces, never increases. Set to 0.01 for interactive flows or 0.02 for retail. maxSizeTolerance is deprecated but still accepted as an alias.
Order status values include PENDING, OPEN, PARTIALLY_FILLED, FILLED, CANCELLED, FAILED, plus trigger lifecycle WAITING_FOR_TRIGGER and TRIGGERED.
List Orders
GET /api/v1/objects/{id}/exchange/orders?status=openOptional status filter (case-insensitive where supported): PENDING, OPEN, PARTIALLY_FILLED, FILLED, CANCELLED, FAILED, WAITING_FOR_TRIGGER, TRIGGERED.
Cancel Order
DELETE /api/v1/objects/{id}/exchange/orders/{orderId}List Positions
GET /api/v1/objects/{id}/exchange/positionsStart a TWAP
POST /api/v1/objects/{id}/exchange/twap
{ "path": "/wallets/main/twap/btc-accumulate", "coin": "hl:BTC", "side": "BUY", "totalSize": "10.0", "durationMinutes": 120, "intervalSeconds": 30, "randomize": false, "reduceOnly": false, "leverage": 5, "slippageBps": 300}Starts a TWAP (Time-Weighted Average Price) order that executes a total size over a duration by placing market orders at regular intervals. Duration can be up to 30 days. Each slice must meet the $10 minimum notional requirement. Active TWAPs are automatically cancelled on account liquidation. Max 5 concurrent TWAPs per exchange object.
Get TWAP Status
GET /api/v1/objects/{id}/exchange/twap/{operationId}Returns TWAP status, progress (executedSize, filledSlices, percentComplete), and the parent operation.
Cancel a TWAP
DELETE /api/v1/objects/{id}/exchange/twap/{operationId}Cancels an active TWAP. Already-executed slices are not reversed.
List TWAPs
GET /api/v1/objects/{id}/exchange/twaps?active=trueLists TWAPs for an exchange object. Use active=true to filter to active TWAPs only.
List Fills (Trade History)
This is the recommended endpoint for displaying trade details. Returns paginated historical fills (trades) for an exchange object, including per-fill P&L, fee breakdown, trade direction, and resulting position state. Similar to Hyperliquid's userFills endpoint but with Arca operation correlation and no 10k fill cap.
For activity feeds that start from operations, use the context field on the operation detail response (GET /api/v1/operations/:id) which provides the same structured data inline without a separate API call.
GET /api/v1/objects/{id}/exchange/fills?market=hl:BTC&limit=100Query parameters: market (filter by market coin, e.g. hl:BTC), startTime / endTime (RFC 3339 timestamps), limit (default 100, max 500), cursor (createdAt of last fill for pagination).
Each fill includes: side (BUY/SELL), size, price, dir ("Open Long", "Close Short", etc.), startPosition, realizedPnl, split fees (exchangeFee, platformFee, builderFee), resultingPosition (side, size, entryPx, leverage), isLiquidation, operationId (the fill operation), and orderOperationId (the originating order placement operation) for correlation spine tracing.
Trade Summary
Per-market P&L aggregation: total realized P&L, total fees, trade count, and total volume, with cross-market totals.
GET /api/v1/objects/{id}/exchange/trade-summaryQuery parameters: startTime / endTime (RFC 3339 timestamps) to scope the summary to a time window.
Each per-market entry includes totalExchangeFees, totalPlatformFees, totalBuilderFees (fee breakdown), totalFundingPaid / totalFundingReceived (cumulative funding), and an optional openPositionCosts object with costs attributed to the current open position using proportional close-out accounting. Closing X% of a position discharges X% of its accumulated costs, so position recycling (close to near-zero and reopen) correctly resets the cost attribution.
Funding History
Lists hourly funding payments applied to an exchange account's positions. Funding payments are calculated using the current Hyperliquid funding rate and applied once per hour. Positive payments credit the account (shorts when the rate is positive); negative payments debit it (longs when the rate is positive).
GET /api/v1/objects/{id}/exchange/funding-history?limit=100Query parameters: limit (default 100, max 500).
Each record includes: coin, positionSide (LONG/SHORT), positionSize, markPrice, fundingRate, payment (signed — negative means the account paid), and balanceAfter.
Market Data (Global)
The sim-exchange mirrors the Hyperliquid perpetuals universe. Available coins are not hard-coded — call GET /exchange/market/meta to discover the current set of supported assets, their max leverage, and size decimals. Use this data for client-side order validation before submission.
GET /api/v1/exchange/market/meta # Market metadata (perps universe)GET /api/v1/exchange/market/mids # Current mid pricesGET /api/v1/exchange/market/tickers # 24h ticker data for all assetsGET /api/v1/exchange/market/book/{coin} # L2 order book ({coin} = canonical market ID, e.g. hl:BTC)GET /api/v1/exchange/market/candles/{coin} # OHLCV candles ({coin} = canonical market ID, e.g. hl:BTC)GET /api/v1/exchange/market/sparklines # Batch sparkline close prices for all assetsMarket Meta Response
GET /exchange/market/meta returns a universe array of assets. Each entry contains:
namestring"hl:BTC", "hl:xyz:TSLA" for HIP-3).symbolstring"BTC", "xyz:TSLA").exchangestring"hl" for Hyperliquid).dexstring?"xyz").szDecimalsnumberszDecimals: 4 means sizes must be multiples of 0.0001. Truncate or round order sizes to this precision.maxLeveragenumberonlyIsolatedbooleantrue, only isolated margin is supported for this asset — cross-margin is not available.candleHistoryobject?earliestMs (absolute earliest candle timestamp, including extended pre-listing history) and hlEarliestMs (earliest venue-native candle timestamp). When earliestMs < hlEarliestMs, extended history is available. Use earliestMs as the startTime for a “Max” chart button. Omitted when history bounds are not yet computed.displayNamestring?"Bitcoin", "Crude Oil", "S&P 500"). Use this for UI labels instead of the raw symbol. Omitted when no curated name is available — fall back to symbol. Curated via the admin panel.logoUrlstring?isHip3boolean?deployerDisplayNamestring?feeScalenumber?1 for standard perps; greater than 1 for builder-deployed perps where the deployer set a deployerFeeScale. Omitted or undefined for standard perps. Use this to display total effective fees to end users.Candles
Returns OHLCV (open, high, low, close, volume) candles for a coin. No authentication required — public market data.
499 REQUEST_CANCELED when the caller cancels the request, 504 EXCHANGE_TIMEOUT when the downstream candle fetch exceeds its deadline, and 502 EXCHANGE_ERROR only for genuine upstream failures.startTime, endTime) and the candle t field. Daily (1d) candles open at UTC midnight boundaries. Other API timestamps (operations, events) use RFC 3339 UTC strings instead.Query Parameters
intervalstringrequired1m, 5m, 15m, 1h, 4h, 1d.startTimenumberendTimenumberResponse
{ "success": true, "data": { "coin": "hl:BTC", "interval": "1m", "candles": [ { "t": 1709827200000, "o": "67123.5", "h": "67150.0", "l": "67100.2", "c": "67140.8", "v": "12.5", "n": 45 } ] }}Candle fields: t (bar open time, Unix epoch ms UTC), o (open), h (high), l (low), c (close), v (volume), n (trade count).
Market Tickers
Returns 24-hour ticker data for all assets in a single call. Includes volume, price change, funding rate, open interest, and delisted status. No authentication required — public market data. Data is refreshed every 30 seconds.
Response
{ "success": true, "data": { "tickers": [ { "coin": "hl:BTC", "markPx": "97500.5", "midPx": "97498.0", "prevDayPx": "96200.0", "dayNtlVlm": "1234567890.50", "priceChange24hPct": "1.3513", "openInterest": "500000000.0", "funding": "0.0001", "nextFundingTime": 1709830800000, "isDelisted": false } ] }}coinstring"hl:BTC").symbolstring"BTC").exchangestring"hl").dexstring?markPxstringmidPxstringprevDayPxstringdayNtlVlmstringpriceChange24hPctstringopenIntereststringfundingstringnextFundingTimenumber?isDelistedbooleanSparklines
Returns recent close-price arrays for all tracked coins in a single request. Designed for rendering inline sparkline charts in asset lists, pickers, or dashboards. Data is server-side cached (~60s). No authentication required — public market data.
Freshness: Sparkline data is pre-computed in the background every ~5 minutes. All tracked coins are always included. For real-time price information, use mid prices (watchPrices) or candle subscriptions (subscribeCandles). Newly listed coins may have fewer data points until candle history accumulates.
No query parameters. The response always contains 24 data points at 1-hour intervals (the last 24 hours of hourly close prices).
Response
{ "success": true, "data": { "sparklines": { "hl:BTC": [60123.5, 60200.0, 60150.8, 60340.2, 60280.5], "hl:ETH": [3012.4, 3018.7, 3010.1, 3025.6, 3021.3] } }}Each key is a canonical market ID and each value is an array of close prices at the requested interval, most recent last. Use these to render SVG sparkline polylines or any miniature chart.
Exchange Permissions
| Action | Description |
|---|---|
arca:PlaceOrder | Place orders on an exchange Arca |
arca:CancelOrder | Cancel orders on an exchange Arca |
arca:ReadExchange | Read exchange state, orders, positions |
arca:Exchange | Alias: PlaceOrder + CancelOrder + ReadExchange |
Nonce Endpoints
Get Next Nonce
POST/api/v1/nonceJWT / API Key
Return the next unused nonce for a given prefix within a realm. Every call atomically increments the counter, whether or not the caller uses the resulting path. This provides a safe, unique number for constructing idempotent operation or object paths.
Request Body
realmIdstringrequiredprefixstringrequiredseparator field (see below).separatorstring/ if the prefix ends with /, otherwise -. Use ":" for operation nonces (e.g., /op/create/wallets/main:1).Response 200 OK
{ "success": true, "data": { "nonce": 1, "path": "/op/create/wallets/main:1" }}Behavior
The counter is realm-scoped and prefix-scoped. Two different prefixes in the same realm maintain independent counters. The counter starts at 1 on first use and increments by 1 on every call. The increment is atomic (Spanner read-write transaction), so concurrent callers are guaranteed unique nonces.
Nonce Best Practices
Nonces are the building block for idempotent, auditable paths in Arca. Follow these conventions so every builder on the platform produces consistent, collision-free paths.
Separator Conventions
| Context | Separator | Example Prefix | Result |
|---|---|---|---|
| Operation nonces | : (colon) | /op/create/wallets/main | /op/create/wallets/main:1 |
| Object name nonces | - (hyphen, default) | /orders/order | /orders/order-47 |
| Child path nonces | (none) — prefix ends with / | /wallets/ | /wallets/3 |
When to Use Nonces
- Operations that may be retried — transfers, deposits, and any create call that should be safely re-submittable.
- Delete + recreate scenarios — because an Arca path can be reused after deletion, the create operation path must include a nonce to remain unique. Pass the resulting path as
operationPathonPOST /api/v1/objects. - Dynamic object paths — use the nonce API to generate the object
pathitself, not just operation paths. A trailing-slash prefix produces sequential children (/wallets/3); a regular prefix with the default hyphen separator produces named sequences (/orders/order-47). Pass the resulting path asref(SDK) orpath(API) when creating the object.
Recommended Pattern: Create with Nonce
// 1. Reserve a nonce for the create operation (colon separator)const { path: opPath } = await arca.nonce('/op/create/wallets/main', ':');// → { nonce: 1, path: "/op/create/wallets/main:1" }
// 2. Store opPath, then use it for the operationconst { object } = await arca.ensureDenominatedArca({ ref: '/wallets/main', denomination: 'USD', operationPath: opPath,});// Safe to retry — same operationPath returns the same result.Recommended Pattern: Object Path with Nonce
Nonces are not limited to operation paths — you can also use them to generate the object path itself. This is useful when your application creates objects dynamically and needs unique, sequential paths without tracking counters client-side.
// Sequential children — trailing-slash prefix omits the separatorconst { path } = await arca.nonce('/wallets/');// → { nonce: 3, path: "/wallets/3" }
await arca.ensureDenominatedArca({ ref: path, denomination: 'USD' });
// Named with sequence — default hyphen separatorconst { path: orderPath } = await arca.nonce('/orders/order');// → { nonce: 47, path: "/orders/order-47" }
await arca.ensureArca({ ref: orderPath, type: 'exchange' });Idempotency Guarantee
The operation path is the unique-per-realm idempotency key. Reusing the same operation path on a subsequent call is a safe retry and returns the original result. A new nonce produces a new, independent operation. The Arca object path alone also provides idempotency for the common case where no delete/recreate is expected — if an active object already exists at the path with matching type and denomination, it is returned without creating a duplicate.
Common Anti-Pattern: Inline Nonce
Every call to POST /api/v1/nonce atomically increments the counter and returns a new, unique path. If you call it inline — inside a retry loop or as part of the operation request itself — each attempt gets a different path, so every retry creates a distinct operation instead of safely replaying the original one.
Correct pattern: Reserve the nonce once, store the resulting path (in a variable, database, or message queue), then use that stored path for the operation. If the operation fails transiently, retry with the same path.
State Delta Endpoints
List State Deltas
GET/api/v1/deltasJWT / API Key
List state deltas for a specific Arca path. Deltas capture before/after values for every state change.
Query Parameters
realmIdstringrequiredarcaPathstringrequiredResponse
{ "success": true, "data": { "deltas": [ { "id": "d1e2f3a4-...", "realmId": "6d25623e-...", "operationId": "e5f6a7b8-...", "eventId": "f1e2d3c4-...", "arcaPath": "/treasury/usd-reserve", "deltaType": "balance_change", "beforeValue": "0", "afterValue": "1000.00", "createdAt": "2026-02-11T21:12:52.529Z" } ], "total": 1 }}Real-time Streaming
WebSocket
GET/api/v1/wsVia message
Subscribe to real-time realm events via WebSocket. Authentication is performed by sending an auth message after the connection is established (not via HTTP headers or query parameters).
Connection Flow
- Open a WebSocket connection to
/api/v1/ws - Send an
authmessage with your credentials and realm ID - Receive an
authenticatedconfirmation - Optionally send
subscribeto filter by channel - Receive enriched events as JSON messages
Auth Message
// API key authentication (backend services){ "action": "auth", "apiKey": "arca_78ae7276_...", "realmId": "6d25623e-..." }
// Builder JWT authentication (interactive/portal){ "action": "auth", "token": "eyJhbGciOiJIUzI1NiIs...", "realmId": "6d25623e-..." }
// Scoped token authentication (end-user frontends)// Uses the same "token" field — realmId is embedded in the token claims{ "action": "auth", "token": "<scoped-jwt>", "realmId": "6d25623e-..." }Scoped tokens use the same token field as builder JWTs. The server detects the token type from its claims and enforces scope restrictions on all events and subscriptions.
On success, the server responds with:
{ "type": "authenticated", "realmId": "6d25623e-..." }Channel Subscriptions
By default, all event types are delivered. Send a subscribe message to filter by channel:
{ "action": "subscribe", "channels": ["operations", "balances", "objects"] }{ "action": "unsubscribe", "channels": ["objects"] }Available channels:
| Channel | Event types |
|---|---|
operations | operation.created, operation.updated |
balances | balance.updated |
objects | object.created, object.updated, object.deleted |
events | event.created |
exchange | exchange.fill, exchange.funding |
aggregation | aggregation.updated, object.valuation |
Server-Sent Snapshots
When you subscribe to the operations or balances channel, the server immediately sends a snapshot message containing the current state. This lets clients render the initial UI without a separate REST call. Subsequent events are incremental updates.
// Snapshot for the "balances" channel (sent automatically on subscribe){ "type": "snapshot", "channel": "balances", "data": [ { "entityId": "obj_01h2...", "entityPath": "/wallets/main", "balances": [{ "id": "bal_...", "arcaId": "obj_01h2...", "denomination": "USD", "amount": "1000.00" }] } ]}Mid Price Subscriptions
Subscribe to live mid price updates for specific coins or all coins. Emits mids.updated events as prices change.
// Subscribe to specific coins{ "action": "subscribe_mids", "exchange": "sim-hl", "coins": ["hl:BTC", "hl:ETH"] }
// Subscribe to all coins (empty coins array){ "action": "subscribe_mids", "exchange": "sim-hl", "coins": [] }
// Unsubscribe{ "action": "unsubscribe_mids" }Candle Subscriptions
Subscribe to real-time OHLCV candle updates for specific coins and intervals. Emits candle.updated (in-progress candle changed) and candle.closed (finalized candle) events.
// Subscribe to candles{ "action": "subscribe_candles", "coins": ["hl:BTC", "hl:ETH"], "intervals": ["1m", "5m"] }
// Unsubscribe{ "action": "unsubscribe_candles" }
// Event payload{ "type": "candle.closed", "coin": "hl:BTC", "interval": "1m", "candle": { "t": 1709312400000, "o": "95432.50", "h": "95500.00", "l": "95400.00", "c": "95480.00", "v": "12.34", "n": 47 }}Object Valuation Watch
Subscribe to real-time valuation updates for a single Arca object. Uses the same computation path as aggregation watches (Axiom 10), guaranteeing that single-object and multi-object valuations are perfectly additive.
// Watch a single object{ "action": "watch_object", "path": "/strategies/alpha" }
// Initial response + subsequent updates{ "type": "object.valuation", "path": "/strategies/alpha", "watchId": "req_abc...", "valuation": { "objectId": "obj_123...", "path": "/strategies/alpha", "type": "exchange", "valueUsd": "5234.56", "balances": [...] }}
// Unwatch{ "action": "unwatch_object", "watchId": "req_abc..." }Connection Example
const ws = new WebSocket("ws://localhost:3052/api/v1/ws");
ws.onopen = () => { ws.send(JSON.stringify({ action: "auth", apiKey: "arca_78ae7276_...", realmId: "6d25623e-...", }));};
ws.onmessage = (e) => { const msg = JSON.parse(e.data);
if (msg.type === "authenticated") { // Optionally filter to specific channels ws.send(JSON.stringify({ action: "subscribe", channels: ["operations", "balances"], })); return; }
// Enriched event — includes actual entity data (no refetch needed) console.log(msg.type, msg.entityId, msg.entityPath); // e.g. msg.operation, msg.object, msg.balances, msg.exchangeState};Enriched Events
Every event includes the full entity data inline, so clients never need to refetch after receiving an event. For example, an operation.updated event includes the full operation object; a balance.updated event includes the current balances array plus any held reserves.
// operation.updated — includes the full operation object{ "realmId": "6d25623e-...", "type": "operation.updated", "entityId": "op-id", "entityPath": "/op/transfer/fund-1", "operation": { "id": "op-id", "type": "transfer", "state": "completed", ... }, "summary": { "objectCount": 12, "operationCount": 48, "eventCount": 94 }}
// balance.updated — includes the current balances array with four-bucket breakdown{ "realmId": "6d25623e-...", "type": "balance.updated", "entityId": "obj_01h2...", "entityPath": "/wallets/main", "balances": [ { "id": "bal_...", "arcaId": "obj_01h2...", "denomination": "USD", "arriving": "200.00", "settled": "800.00", "departing": "0.00", "total": "1000.00" } ]}
// exchange.fill — includes the fill details{ "realmId": "6d25623e-...", "type": "exchange.fill", "entityId": "ord_01h2...", "entityPath": "/exchanges/hl1", "fill": { "id": "fil_...", "orderId": "ord_...", "coin": "hl:BTC", "side": "BUY", "price": "95432.50", "size": "0.01", ... }}
// exchange.funding — periodic funding payment applied to a position{ "realmId": "6d25623e-...", "type": "exchange.funding", "entityId": "obj_01h2...", "entityPath": "/exchanges/hl1", "funding": { "accountId": "act_01h2...", "coin": "hl:BTC", "side": "LONG", "size": "0.5", "price": "95432.50", "fundingRate": "0.0001", "payment": "-4.77" }}Summary
Get Summary
GET/api/v1/summaryJWT / API Key
Get aggregate counts for objects, operations, and events in a realm. Useful for dashboard widgets.
Query Parameters
realmIdstringrequiredResponse
{ "success": true, "data": { "objectCount": 12, "operationCount": 47, "eventCount": 93 }}Get Reconciliation State
GET/api/v1/reconciliationJWT / API Key
Returns platform-expected versus venue-reported balances/positions for exchange objects in a realm.
Agent (AI)
The Agent endpoint provides a conversational AI assistant that can inspect your realm's current state (objects, balances, operations, exchange positions) and propose executable multi-step plans. The agent uses read-only tools to gather context and never mutates state directly — all proposed operations are returned as structured plans for the client to execute after user approval.
Agent Status
GET/api/v1/agent/statusJWT / API Key
Returns whether the agent feature is enabled (requires AGENT_ANTHROPIC_API_KEY or AGENT_GEMINI_API_KEY environment variable).
{ "data": { "enabled": true } }Chat
POST/api/v1/agent/chatJWT / API Key
Send a message to the agent. Returns a JSON response with the chatId and processes the conversation asynchronously. Real-time updates (text, tool use, plans) are delivered over the WebSocket connection on the agent channel.
Subscribe to agent events by sending {"action":"subscribe","channels":["agent"]} on the /api/v1/ws WebSocket. The following event types are emitted:
agent.text— Streamed text content from the assistantagent.tool_use— Shows which tool the agent is calling (e.g. browsing objects, checking balances)agent.plan— A structured plan with executable stepsagent.conversation_log— Updated conversation logagent.done— Agent turn complete
Request Body
{ "realmId": "uuid", "chatId": "chat-id", "messages": [ { "role": "user", "content": "Transfer 500 from user-2/main to their exchange and open a 10x BTC long" } ]}Response
{ "success": true, "data": { "chatId": "chat-id" } }Plan Step Format
Each step in a proposed plan contains:
{ "id": "step-1", "description": "Transfer 500 USD from /user-2/main to /user-2/exchange", "operation": "transfer", "params": { "from": "/user-2/main", "to": "/user-2/exchange", "amount": "500" }, "dependsOn": []}Supported operations: transfer, deposit, createObject,deleteObject, placeOrder, cancelOrder, setLeverage.
Payment Link Endpoints
Payment links let builders create shareable URLs for deposits and withdrawals. End users open the link, see a preview, and complete the payment — no authentication required on their side. The builder creates the link (authenticated), and the end user interacts with it via the token-based public endpoints.
Create Payment Link
POST/api/v1/payment-linksJWT / API Key
Create a new payment link. Returns the link URL, a unique token, and the associated pending operation. The link expires after the specified duration (default 24 hours).
Request Body
realmIdstringrequiredtypestringrequired"deposit" or "withdrawal".arcaPathstringrequiredamountstringrequired"100.00").returnUrlstringreturnStrategy is "redirect" or "navigate". Supports HTTP(S) URLs and custom URL schemes (e.g., myapp://callback). The schemes javascript:, data:, vbscript:, and blob: are blocked.returnStrategystring"redirect", "navigate", or "close". Defaults to "redirect" when returnUrl is provided. "redirect" auto-redirects after 2.5 seconds and shows a Continue link (requires an HTTP/HTTPS returnUrl). "navigate" shows a Continue button that calls window.location.href — use for custom URL schemes in native apps. "close" shows a Continue button that calls window.close() — use for native apps that embed the payment page in SFSafariViewController or Android Custom Tabs and detect dismissal to reload state.expiresInMinutesnumbermetadatastringResponse 201 Created
{ "success": true, "data": { "paymentLink": { "id": "pl_01h2xcejqtf2nbrexx3vqjhp41", "url": "https://app.arcaos.io/pay/abc123def456", "token": "abc123def456", "type": "deposit", "status": "pending", "amount": "100.00", "denomination": "USD", "operationId": "op_01h2xcejqtf2nbrexx3vqjhp42", "expiresAt": "2026-03-08T21:00:00.000000Z", "returnUrl": "https://example.com/thanks", "returnStrategy": "redirect", "createdAt": "2026-03-07T21:00:00.000000Z" }, "operation": { "id": "op_01h2xcejqtf2nbrexx3vqjhp42", "type": "deposit", "state": "pending", ... } }}List Payment Links
GET/api/v1/payment-linksJWT / API Key
List payment links in a realm, optionally filtered by type and status.
Query Parameters
realmIdstringrequiredtypestring"deposit" or "withdrawal".statusstring"pending", "completed", or "expired".Response
{ "success": true, "data": { "paymentLinks": [ { "id": "pl_01h2xcejqtf2nbrexx3vqjhp41", "url": "https://app.arcaos.io/pay/abc123def456", "type": "deposit", "status": "pending", "amount": "100.00", "denomination": "USD", "operationId": "op_01h2xcejqtf2nbrexx3vqjhp42", "expiresAt": "2026-03-08T21:00:00.000000Z", "returnUrl": "https://example.com/thanks", "returnStrategy": "redirect", "createdAt": "2026-03-07T21:00:00.000000Z" } ], "total": 1 }}Preview Payment Link
GET/api/v1/payment-links/:token
Public endpoint — no authentication required. Retrieves a payment link preview by its token. Returns only the information needed to display the payment page (realm name, type, amount, status, branding). Sensitive fields (realm ID, Arca path) are omitted.
Path Parameters
tokenstringrequiredResponse
{ "success": true, "data": { "realmName": "Production", "type": "deposit", "amount": "100.00", "denomination": "USD", "status": "pending", "expiresAt": "2026-03-08T21:00:00.000000Z", "returnUrl": "https://example.com/thanks", "returnStrategy": "redirect", "branding": { "displayName": "Acme Pay", "accentColor": "#4F46E5", "logoUrl": "https://example.com/logo.png", "hideExpiryTimer": false, "environmentBannerText": "Sandbox mode" } }}The branding field is present only when the realm has branding configured via PATCH /api/v1/realms/:id/settings. All branding fields are optional and omitted when not set. When no branding is configured, the payment page uses default Arca branding.
Complete Payment Link
POST/api/v1/payment-links/:token/complete
Public endpoint — no authentication required. Completes a pending payment link, triggering the underlying deposit or withdrawal operation. The response includes the operationId of the settlement operation. For withdrawals, the on-chain transaction settles asynchronously — poll the status endpoint below to determine the final outcome.
Path Parameters
tokenstringrequiredResponse
{ "success": true, "data": { "status": "completed", "operationId": "op_01abc...", "returnUrl": "https://example.com/thanks", "returnStrategy": "redirect" }}Payment Link Operation Status
GET/api/v1/payment-links/:token/status
Public endpoint — no authentication required. Returns the current state of the settlement operation associated with the payment link. Use this to poll for the final outcome after calling the complete endpoint. For withdrawals, the operation remains pending until on-chain settlement finishes.
Path Parameters
tokenstringrequiredResponse
{ "success": true, "data": { "operationState": "completed", "outcome": null }}Possible values for operationState: pending, completed, failed, expired. When the state is failed, the outcome field contains a JSON string with details (e.g. {"message":"...","reason":"..."}).
Org & Team Endpoints
Manage your organization, team members, and invitations. All endpoints require JWT authentication and check account-level permissions.
Create Organization
POST/api/v1/orgsJWT
Create a new organization. The authenticated user becomes the owner. Returns the organization and membership.
Request Body
namestringrequiredResponse 201 Created
{ "success": true, "data": { "organization": { "id": "org-uuid", "name": "Acme Inc", "slug": "acme-inc", "createdAt": "2026-02-22T00:00:00Z" }, "membership": { "id": "member-id", "userId": "user-uuid", "role": "owner", "createdAt": "2026-02-22T00:00:00Z" } }}Get Organization
GET/api/v1/orgJWT
Returns the current user's organization.
{ "success": true, "data": { "id": "org-uuid", "name": "Acme Inc", "slug": "acme-inc", "createdAt": "2026-02-22T00:00:00Z" }}List Members
GET/api/v1/org/membersJWT
Lists all members in the organization with their roles and realm type scopes.
{ "success": true, "data": { "members": [ { "id": "member-id", "userId": "user-uuid", "email": "alice@example.com", "memberType": "user", "role": "admin", "realmTypeScope": ["production", "staging"], "createdAt": "2026-02-22T00:00:00Z" } ], "total": 1 }}Invite Member
POST/api/v1/org/invitationsJWT
Send an invitation email. Requires account:ManageMembers permission. The inviter cannot assign a role higher than their own.
Request Body
{ "email": "bob@example.com", "role": "developer", "realmTypeScope": ["development", "testing"]}Update Member
PATCH/api/v1/org/membersJWT
Update a member's role or realm type scope. Privilege escalation protection applies.
Request Body
{ "memberId": "member-id", "role": "admin", "realmTypeScope": null}Remove Member
DELETE/api/v1/org/membersJWT
Remove a member from the organization. Cannot remove the owner.
Query Parameters
memberIdstringrequiredTransfer Ownership
POST/api/v1/org/transfer-ownershipJWT
Transfer organization ownership to another member. Only the current owner can do this.
Request Body
{ "newOwnerUserId": "user-uuid" }Status
Weekly Reset Schedule
GET/api/v1/status/resetNone
Returns the next and last weekly reset timestamps for the Developer Preview environment. Resets occur every Sunday at 00:00 UTC and wipe all data (accounts, realms, objects, API keys, operations). No authentication is required.
Response 200 OK
{ "success": true, "data": { "nextReset": "2026-03-15T00:00:00.000000Z", "lastReset": "2026-03-08T00:00:00.000000Z" }}nextResetstringlastResetstring