Arca/Documentation
Join Waitlist

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.

json
// 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

CodeMeaning
200Success
201Created — resource successfully created
400Bad Request — validation error in your input
401Unauthorized — missing or invalid authentication
403Forbidden — authenticated but insufficient permissions (scope)
404Not Found — resource doesn't exist or isn't accessible
409Conflict — duplicate resource or invalid state transition
500Internal Server Error — unexpected failure
502Bad Gateway — upstream service error (e.g., exchange unavailable)
503Service 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

emailstringrequired
Valid email address.
passwordstringrequired
Minimum 8 characters.
orgNamestringrequired
Organization name.

Response

json
{
"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

emailstringrequired
Email address.
passwordstringrequired
Password.

Response

json
{
"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

googleIdTokenstringrequired
Google ID token from the OAuth flow.
orgNamestring
Organization name. Required when the user is new (first sign-in). Omit for existing users.

Response

json
{
"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

refreshTokenstringrequired
The opaque refresh token received from sign-in or a previous refresh.

Response

json
{
"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

refreshTokenstringrequired
The refresh token to revoke.

Response

json
{
"success": true,
"data": {
"loggedOut": true
}
}

Get Profile

GET/api/v1/auth/meJWT

Retrieve the authenticated builder's profile.

Response

json
{
"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

realmIdstringrequired
The realm this token is locked to.
substringrequired
Opaque end-user identifier (builder-defined, e.g., user ID). Recorded as the actor in the audit trail.
scopeobjectrequired
Policy scope with a statements array. Each statement has effect ("Allow" or "Deny", default "Allow"), actions (action strings or aliases), and resources (path patterns).
expirationMinutesnumber
Token TTL in minutes (1–1440). Default: 60.

Response 201 Created

json
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"expiresAt": "2026-02-14T15:00:00Z"
}
}

Example: Read-only token (recommended default)

bash
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

namestringrequired
Realm name (max 100 characters). A URL-safe slug is generated automatically.
typestring
Either "demo" (default) or "production".
descriptionstring
Optional description.

Response 201 Created

json
{
"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

json
{
"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

idstringrequired
Realm ID (UUID).

Delete Realm

DELETE/api/v1/realms/:idJWT / API Key

Permanently delete a realm. This cannot be undone.

Response

json
{
"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 | null
Default builder fee in tenths of a basis point (e.g. 45 = 4.5 bps) applied to exchange orders when no per-order override is specified. Set to null to clear.
brandingobject | null
White-label branding for the payment portal. All sub-fields are optional. Set to null to clear.
branding.displayNamestring
Display name shown on the payment page header. Falls back to the realm name. Max 100 characters.
branding.accentColorstring
Hex color for primary buttons and icons (e.g. "#4F46E5"). Must be a 6-digit hex color with # prefix.
branding.logoUrlstring
HTTPS URL to a logo image displayed in the payment page header.
branding.hideExpiryTimerboolean
When true, the expiry countdown is hidden on the payment page.
branding.environmentBannerTextstring
Custom text for the environment banner on non-production realms. Max 200 characters.

Response

json
{
"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

namestringrequired
Human-readable name for the key (max 100 characters).

Response 201 Created

json
{
"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..."
}
}
Important

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

json
{
"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

idstringrequired
API key ID (UUID).

Response

json
{
"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

json
{
"name": "production-key-2026",
"publicKey": "MCowBQYDK2VwAyEA..."
}
namestringrequired
Display name for the key.
publicKeystringrequired
Base64-encoded Ed25519 public key (32 bytes).

Response

json
{
"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

json
{
"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

json
{
"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

json
{
"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

json
{
"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:

HeaderValue
X-Arca-Key-IdThe key fingerprint (returned at registration)
X-Arca-SignatureBase64-encoded Ed25519 signature
X-Arca-TimestampUnix timestamp (seconds) — must be within 5 minutes of server time

Signing Protocol

The signature is computed over a signing input constructed as:

text
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

typescript
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 alphabetically
const 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

realmIdstringrequired
Realm ID to create the object in.
pathstringrequired
Hierarchical path (e.g., /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
One of "denominated" (default), "exchange", "deposit", "withdrawal", "escrow".
denominationstring
Currency or asset denomination (e.g., "USD", "BTC"). Relevant for denominated objects.
metadatastring
Optional JSON metadata string.
operationPathstring
Optional operation path serving as the idempotency key. Use the nonce API with separator ":" 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)

json
{
"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)

json
{
"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

realmIdstringrequired
Realm ID.
prefixstring
Path prefix to filter (e.g., /treasury).
includeDeletedboolean
Include deleted and deleting objects. Defaults to false.
includestring
Comma-separated list of optional fields to include on each object. Use labels to include labels.

Response

json
{
"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

realmIdstringrequired
Realm ID.
prefixstring
Path prefix to browse. Defaults to "/".
includeDeletedboolean
Include deleted and deleting objects in the results. Defaults to false.

Response

json
{
"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

realmIdstringrequired
Realm ID.
pathstringrequired
Full isolation-zone root path, such as /users/alice. Root / is not allowed because it is already the realm's default boundary.

Response

json
{
"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).

realmIdstringrequired
Realm ID.
pathstringrequired
Exact path of the Arca object (e.g. /strategies/alpha).
json
{
"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

realmIdstringrequired
Realm ID.
prefixstringrequired
Path prefix to aggregate (e.g. /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

json
{
"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

realmIdstringrequired
Realm ID.
prefixstringrequired
Path prefix or exact object path. Trailing slash = prefix match (children). No trailing slash = exact match (single object).
fromstringrequired
Start of the time range (RFC 3339).
tostringrequired
End of the time range (RFC 3339).
pointsnumber
Number of samples (default 200, max 1000).

P&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

realmIdstringrequired
Realm ID.
prefixstringrequired
Path prefix or exact object path. Trailing slash = prefix match (children). No trailing slash = exact match (single object).
fromstringrequired
Start of the period (RFC 3339).
tostringrequired
End of the period (RFC 3339).

P&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

realmIdstringrequired
Realm ID.
prefixstringrequired
Path prefix or exact object path. Trailing slash = prefix match (children). No trailing slash = exact match (single object).
fromstringrequired
Start of the period (RFC 3339).
tostringrequired
End of the period (RFC 3339).
pointsnumber
Number of data points (default 200).

Response

json
{
"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

POST/api/v1/aggregations/watchJWT / API Key

Request Body

json
{
"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>" }
]
}
realmIdstringrequired
Realm ID.
sourcesarrayrequired
List of aggregation sources. Each source has a type (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

json
{
"success": true,
"data": {
"watchId": "a1b2c3d4-...",
"aggregation": {
"prefix": "(watch)",
"totalEquityUsd": "15734.56",
"departingUsd": "500",
"breakdown": [...],
"objects": [...]
}
}
}

Get Watch Aggregation

GET/api/v1/aggregations/watch/:watchIdJWT / API Key

Returns 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

DELETE/api/v1/aggregations/watch/:watchIdJWT / API Key

Removes 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

idstringrequired
Object ID (UUID).

Response

json
{
"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

idstringrequired
Object ID (UUID).

Response

json
{
"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

idstringrequired
Object ID (UUID).

Query Parameters

realmIdstringrequired
Realm ID.
asOfstringrequired
RFC3339 timestamp. Returns latest rows at or before this time.

Response

json
{
"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

realmIdstringrequired
Realm ID.

Delete 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

realmIdstringrequired
Realm ID.
objectIdstring
ID of the Arca object to delete. Provide either objectId or path.
pathstring
Path of the Arca object to delete. Alternative to objectId.
sweepToPathstring
Optional Arca path to sweep remaining funds to before deleting. Required if the object has non-zero balances (including exchange balances).
liquidatePositionsboolean
Set to true to liquidate all open exchange positions via market orders before deletion. Required for exchange objects with open positions. Defaults to false.

Response 200 OK

json
{
"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

idstringrequired
Object ID (UUID) of any version.

Response

json
{
"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

idstringrequired
Object ID (UUID).

Query Parameters

realmIdstringrequired
Realm ID.

Request Body

json
{
"labels": {
"displayName": "Main Wallet",
"tier": "gold",
"removeMe": null
}
}
labelsRecord<string, string | null>required
Label key-value pairs to set or remove. Set a key to null 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

json
{
"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).

idstring
Unique object ID.
realmIdstring
Realm the object belongs to.
pathstring
Hierarchical Arca path.
displayNamestring | null
Optional UI label. For deleted objects this is a tombstone name such as wallet.deleted.YYYY.MM.DD.SSSSS so repeated generations at the same path stay distinguishable while the canonical path remains unchanged.
typestring
Object type: denominated, exchange, deposit, withdrawal, escrow.
denominationstring | null
Currency or asset denomination.
statusstring
Object status: active, deleting, deleted, withdrawn.
deletedAtstring | null
Terminal deletion timestamp. Deleted paths may later be reused for a new generation.
withdrawnAtstring | null
Terminal withdrawal timestamp. Set only after a full withdrawal retires the object; withdrawn paths are preserved for history but cannot be reused.
metadatastring | null
Optional JSON metadata.
labelsRecord<string, string> | null
Key-value labels. null when not loaded (list without ?include=labels) or when no labels have been set.
createdAtstring
RFC 3339 creation timestamp.
updatedAtstring
RFC 3339 last-updated timestamp.

Label 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.

typescript
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.

typescript
await arca.updateLabels(wallet.id, {
stripeCustomerId: 'cus_abc123',
internalUserId: 'u-789',
});

Categorization

Tag objects by purpose or region for client-side grouping and filtering.

typescript
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.

typescript
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.

typescript
await arca.updateLabels(wallet.id, {
supportTicket: 'TICKET-4521',
flaggedReason: 'suspicious-activity',
});
// Clear the flag when resolved
await 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

realmIdstringrequired
Realm ID.
typestring
Filter by a single operation type (e.g., "deposit").
typesstring
Filter by multiple operation types, comma-separated (e.g., "fill,transfer"). Takes precedence over type.
includeContextstring
When "true", each operation includes its typed context inline (transfer amount/fee, fill details, etc.).

Response

json
{
"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

idstringrequired
Operation ID (UUID).

Query Parameters

includeEvidencestring
When "true", includes a Phase 1 audit evidence bundle with journal rows, proof references, chain transaction metadata, and integrity notes.

Response — Fill (Trade Execution)

json
{
"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

json
{
"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

json
{
"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 proves: the response packages correlated operation history, journal rows, proof references, and chain transaction metadata already recorded by Arca.
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

realmIdstringrequired
Realm ID.
fromstringrequired
Inclusive RFC 3339 timestamp for the export window start.
tostringrequired
Inclusive RFC 3339 timestamp for the export window end.
typestring
Filter by a single operation type.
typesstring
Filter by multiple operation types, comma-separated. Takes precedence over type.
arcaPathstring
Filter by source or target Arca path prefix.
pathstring
Filter by operation path prefix.
limitinteger
Page size. Defaults to 100; maximum 500.
cursorstring
Pagination cursor returned as nextCursor from the previous page. Pass it back verbatim to fetch the next page.

Response

json
{
"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": { ... }
}
]
}
}
The export is JSON-only in Phase 1 and is designed for inspection, preservation, and external review. It is not yet a cryptographic attestation of sequence integrity or reserves.

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

realmIdstringrequired
Realm ID.

Response

json
{
"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

idstringrequired
Event ID (UUID).

Response

json
{
"success": true,
"data": {
"event": { "id": "...", "type": "deposit.completed", ... },
"operation": { "id": "...", "type": "deposit", "state": "completed", ... },
"deltas": [
{ "id": "...", "deltaType": "balance_change", ... }
]
}
}

Fund Account Endpoints

Developer Tool

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 poolAddress and tokenAddress. The operation stays pending until real tokens arrive on-chain. If no tokens are received, the operation remains pending indefinitely.

Request Body

realmIdstringrequired
Realm ID.
arcaPathstringrequired
Path of the target Arca object.
amountstringrequired
Amount as a decimal string (e.g., "1000.00").
pathstring
Optional operation path for idempotency. If provided and an operation already exists at this path, the existing operation is returned instead of creating a duplicate.

Response 201 Created

json
{
"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

poolAddressstring
The on-chain address to send tokens to.
tokenAddressstring
The ERC-20 token contract address (e.g., USDC).
chainstring
Chain identifier (e.g., "reth" for demo, "arbitrum" for production).
expiresAtstring
Expiration timestamp. The on-chain transfer must arrive before this time.

On-Chain Funding Flow

  1. Call POST /fund-account to create a funding intent
  2. Response includes poolAddress — send USDC to this address on-chain
  3. The platform monitors the chain for incoming transfers
  4. When the transfer is detected, a confirmation workflow waits for N block confirmations
  5. After confirmation, the Arca object is credited with the actual on-chain amount
  6. A deposit.confirmed event 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

Developer Tool

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

realmIdstringrequired
Realm ID.
arcaPathstringrequired
Path of the source Arca object.
amountstringrequired
Amount as a decimal string.
destinationAddressstring
On-chain destination address. Required when custody is active.
pathstring
Optional operation path for idempotency.

Response 201 Created

json
{
"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

realmIdstringrequired
Realm ID.

Response

json
{
"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

realmIdstringrequired
Realm ID.

Response

json
{
"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

realmIdstringrequired
Realm ID.

List 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

realmIdstringrequired
Realm ID.
boundaryIdstring
Filter to a specific boundary.

Response

json
{
"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

realmIdstringrequired
Realm ID.
boundaryIdstringrequired
Boundary to assign the recovery key to.
walletAddressstringrequired
Ethereum address of the recovery key wallet.

Response

json
{
"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

realmIdstringrequired
Realm ID.
pathstringrequired
Caller-specified operation path (e.g., "/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
Path of the source Arca object to debit (e.g., "/alice/wallet").
targetArcaPathstringrequired
Path of the target Arca object to credit (e.g., "/bob/wallet").
amountstringrequired
Transfer amount as a decimal string (e.g., "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

json
{
"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_ERROR
Source and target are the same, amount is not positive, denomination mismatch, insufficient balance, exchange-to-exchange transfer, or operation path is blank.
404NOT_FOUND
Source or target Arca object not found at the given path, or realm not found.

Example: curl

bash
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.

json
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:

FieldMeaningNon-zero when
settledValue available for new operationsObject holds funds that are not currently in transit
departingValue committed to leave via an outbound transferAn outbound transfer is pending (hold placed, not yet settled)
arrivingValue in transit toward this object, past the point of no return at the sourceAn inbound transfer to an exchange object is pending (venue has not yet confirmed receipt)
totalarriving + settled + departingAlways (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, and departing separately with labels (e.g. "Available", "Incoming", "Outgoing").
  • Notification-based — show total as the primary balance, but display an inline indicator or toast when arriving or departing is 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 pending and 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:

json
{
"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

bash
GET /api/v1/fees/estimate?realmId=...&action=transfer&amount=1000&sourcePath=/wallets/main&targetPath=/exchange/main

Estimate the fee for a transfer or order before executing it. Returns the fee amount, denomination, and net amount after fee deduction.

realmIdstringrequired
Realm ID.
actionstringrequired
Action type: transfer or order.
amountstringrequired
Amount as a decimal string.
sourcePathstring
Source Arca path (optional).
targetPathstring
Target Arca path (optional).
json
{
"success": true,
"data": {
"fee": { "amount": "0.05", "denomination": "USD" },
"netAmount": "999.95"
}
}

Exchange Account State

bash
GET /api/v1/objects/{id}/exchange/state

Returns 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
equitystring
Total account value: deposited funds + unrealized PnL.
totalRawUsdstring
Total deposited USD before PnL.
availableToWithdrawstring
Withdrawable amount: equity minus maintenance margin, floored at zero. Not free margin for new trades — use equity - initialMarginUsed or the active asset data endpoint for trading capacity.
initialMarginUsedstring
Margin locked in open positions.
maintenanceMarginRequiredstring
Minimum margin before liquidation.
totalUnrealizedPnlstring
Sum of unrealized PnL across positions.
totalNtlPosstring
Sum of absolute notional position sizes.

Active Asset Data (Max Order Size)

bash
GET /api/v1/objects/{id}/exchange/active-asset-data?coin=BTC

Returns 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
coinstring
Asset name.
leverageobject
Current leverage setting: {type, value}. type is "cross" or "isolated".
maxBuySizestring
Max buy size in tokens (positive).
maxSellSizestring
Max sell size in tokens (positive).
maxBuyUsdstring
Max buy size in USD (positive).
maxSellUsdstring
Max sell size in USD (positive).
availableToTradestring
Raw available margin in USD (equity minus margin in use). Direction-agnostic — use for buying power display.
markPxstring
Current mark price.
feeRatestring
All-in fee rate as a decimal. Includes exchange taker fee, platform fee, and builder fee.

Asset Fees

bash
GET /api/v1/objects/{id}/exchange/asset-fees
GET /api/v1/objects/{id}/exchange/asset-fees?builderFeeBps=40

Returns 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
Canonical market ID (e.g. "hl:BTC").
takerFeeRatestring
Effective taker fee rate as a decimal (e.g. "0.00045" = 4.5 bps).
makerFeeRatestring
Effective maker fee rate as a decimal (e.g. "0.00015" = 1.5 bps).

Update Leverage

json
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

bash
GET /api/v1/objects/{id}/exchange/leverage # All coins
GET /api/v1/objects/{id}/exchange/leverage?coin=hl:BTC # Single coin

Place Order (Operation)

json
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:

json
{
"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

bash
GET /api/v1/objects/{id}/exchange/orders?status=open

Optional status filter (case-insensitive where supported): PENDING, OPEN, PARTIALLY_FILLED, FILLED, CANCELLED, FAILED, WAITING_FOR_TRIGGER, TRIGGERED.

Cancel Order

bash
DELETE /api/v1/objects/{id}/exchange/orders/{orderId}

List Positions

bash
GET /api/v1/objects/{id}/exchange/positions

Start a TWAP

json
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

bash
GET /api/v1/objects/{id}/exchange/twap/{operationId}

Returns TWAP status, progress (executedSize, filledSlices, percentComplete), and the parent operation.

Cancel a TWAP

bash
DELETE /api/v1/objects/{id}/exchange/twap/{operationId}

Cancels an active TWAP. Already-executed slices are not reversed.

List TWAPs

bash
GET /api/v1/objects/{id}/exchange/twaps?active=true

Lists 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.

bash
GET /api/v1/objects/{id}/exchange/fills?market=hl:BTC&limit=100

Query 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.

bash
GET /api/v1/objects/{id}/exchange/trade-summary

Query 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).

bash
GET /api/v1/objects/{id}/exchange/funding-history?limit=100

Query 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.

bash
GET /api/v1/exchange/market/meta # Market metadata (perps universe)
GET /api/v1/exchange/market/mids # Current mid prices
GET /api/v1/exchange/market/tickers # 24h ticker data for all assets
GET /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 assets

Market Meta Response

GET /exchange/market/meta returns a universe array of assets. Each entry contains:

namestring
Venue-qualified canonical market ID (e.g. "hl:BTC", "hl:xyz:TSLA" for HIP-3).
symbolstring
The raw venue symbol without the exchange prefix (e.g. "BTC", "xyz:TSLA").
exchangestring
Exchange identifier (e.g. "hl" for Hyperliquid).
dexstring?
HIP-3 DEX name. Omitted for native Hyperliquid perps. Present for builder-deployed DEX assets (e.g. "xyz").
szDecimalsnumber
Number of decimal places for order size precision. For example, szDecimals: 4 means sizes must be multiples of 0.0001. Truncate or round order sizes to this precision.
maxLeveragenumber
Maximum leverage multiplier for this asset. Varies by asset — do not hard-code. Orders with leverage exceeding this value are rejected.
onlyIsolatedboolean
If true, only isolated margin is supported for this asset — cross-margin is not available.
candleHistoryobject?
Candle history availability for this asset. Contains 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?
Human-readable name for the asset (e.g. "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?
CDN URL for the asset logo image. Use for asset icons in UI. Omitted when no curated logo is available. Managed via the admin panel.
isHip3boolean?
Whether this asset is a HIP-3 deployer market (builder-deployed perpetual). Use this to distinguish native venue perps from deployer markets in asset pickers and UI badges.
deployerDisplayNamestring?
The deployer's full display name for HIP-3 assets. Omitted for native perps.
feeScalenumber?
HIP-3 fee multiplier. 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.

Error semantics: This endpoint now distinguishes client-aborted and slow downstream reads during candle bootstrap or backfill. Expect 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.
Time format: All market data timestamps use Unix epoch milliseconds (UTC) — both the query parameters (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
intervalstringrequired
Candle interval: 1m, 5m, 15m, 1h, 4h, 1d.
startTimenumber
Start time in Unix epoch milliseconds (UTC).
endTimenumber
End time in Unix epoch milliseconds (UTC).
Response
json
{
"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
json
{
"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
Canonical market ID (e.g. "hl:BTC").
symbolstring
Raw venue symbol without exchange prefix (e.g. "BTC").
exchangestring
Exchange identifier (e.g. "hl").
dexstring?
HIP-3 DEX name when applicable, omitted for native perps.
markPxstring
Current mark price.
midPxstring
Current mid-market price.
prevDayPxstring
Mark price 24 hours ago.
dayNtlVlmstring
24-hour notional volume in USD.
priceChange24hPctstring
24-hour price change percentage.
openIntereststring
Total open interest in USD.
fundingstring
Current funding rate.
nextFundingTimenumber?
Unix timestamp in milliseconds of the next funding event. Use this to build a countdown timer.
isDelistedboolean
Whether this asset has been delisted. Delisted assets have no active trading and should be filtered from asset pickers.

Sparklines

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
json
{
"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

ActionDescription
arca:PlaceOrderPlace orders on an exchange Arca
arca:CancelOrderCancel orders on an exchange Arca
arca:ReadExchangeRead exchange state, orders, positions
arca:ExchangeAlias: 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

realmIdstringrequired
Realm ID.
prefixstringrequired
The path prefix to nonce. The separator between the prefix and the nonce number depends on the separator field (see below).
separatorstring
Override the character placed between the prefix and the nonce number. When omitted, the default logic applies: / if the prefix ends with /, otherwise -. Use ":" for operation nonces (e.g., /op/create/wallets/main:1).

Response 200 OK

json
{
"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

ContextSeparatorExample PrefixResult
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 operationPath on POST /api/v1/objects.
  • Dynamic object paths — use the nonce API to generate the object path itself, 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 as ref (SDK) or path (API) when creating the object.

Recommended Pattern: Create with Nonce

typescript
// 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 operation
const { 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.

typescript
// Sequential children — trailing-slash prefix omits the separator
const { path } = await arca.nonce('/wallets/');
// → { nonce: 3, path: "/wallets/3" }
await arca.ensureDenominatedArca({ ref: path, denomination: 'USD' });
// Named with sequence — default hyphen separator
const { 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

Never call nonce inline with an operation

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

realmIdstringrequired
Realm ID.
arcaPathstringrequired
Arca object path.

Response

json
{
"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

  1. Open a WebSocket connection to /api/v1/ws
  2. Send an auth message with your credentials and realm ID
  3. Receive an authenticated confirmation
  4. Optionally send subscribe to filter by channel
  5. Receive enriched events as JSON messages

Auth Message

json
// 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:

json
{ "type": "authenticated", "realmId": "6d25623e-..." }

Channel Subscriptions

By default, all event types are delivered. Send a subscribe message to filter by channel:

json
{ "action": "subscribe", "channels": ["operations", "balances", "objects"] }
{ "action": "unsubscribe", "channels": ["objects"] }

Available channels:

ChannelEvent types
operationsoperation.created, operation.updated
balancesbalance.updated
objectsobject.created, object.updated, object.deleted
eventsevent.created
exchangeexchange.fill, exchange.funding
aggregationaggregation.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.

json
// 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.

json
// 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.

json
// 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.

json
// 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

javascript
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.

json
// 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

realmIdstringrequired
Realm ID.

Response

json
{
"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 assistant
  • agent.tool_use — Shows which tool the agent is calling (e.g. browsing objects, checking balances)
  • agent.plan — A structured plan with executable steps
  • agent.conversation_log — Updated conversation log
  • agent.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

json
{ "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.

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

namestringrequired
Organization name.

Response 201 Created

json
{
"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.

json
{
"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.

json
{
"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

json
{
"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

json
{
"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

memberIdstringrequired
The member ID to remove.

Transfer Ownership

POST/api/v1/org/transfer-ownershipJWT

Transfer organization ownership to another member. Only the current owner can do this.

Request Body

json
{ "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

json
{
"success": true,
"data": {
"nextReset": "2026-03-15T00:00:00.000000Z",
"lastReset": "2026-03-08T00:00:00.000000Z"
}
}
nextResetstring
ISO 8601 timestamp of the next scheduled reset.
lastResetstring
ISO 8601 timestamp of the most recent reset.