Arca/Documentation
Portal

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"
}
}

HTTP Status Codes

CodeMeaning
200Success
201Created — resource successfully created
400Bad Request — validation error in your input
401Unauthorized — missing or invalid authentication
404Not Found — resource doesn't exist or isn't accessible
409Conflict — duplicate resource or invalid state transition
500Internal Server Error — unexpected failure

Auth Endpoints

Sign Up

POST/api/v1/auth/signup

Create a new builder account. Returns a JWT token on success.

Request Body

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

Response

json
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"builder": {
"id": "54248205-1cc7-4437-a76c-95d2b7bf07e0",
"email": "you@company.com",
"orgName": "Acme Trading"
}
}
}

Sign In

POST/api/v1/auth/signin

Authenticate an existing builder. Returns a JWT token.

Request Body

emailstringrequired
Email address.
passwordstringrequired
Password.

Response

json
{
"success": true,
"data": {
"token": "eyJhbGciOiJIUzI1NiIs...",
"builder": {
"id": "54248205-1cc7-4437-a76c-95d2b7bf07e0",
"email": "you@company.com",
"orgName": "Acme Trading"
}
}
}

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"
}
}

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.

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.

Response

json
{
"success": true,
"data": {
"id": "6d25623e-...",
"builderId": "54248205-...",
"name": "Development",
"slug": "development",
"type": "demo",
"description": "My development environment",
"settings": {
"defaultBuilderFeeBps": 5
},
"createdAt": "2026-02-11T...",
"updatedAt": "2026-02-22T..."
}
}

API Key Endpoints

All API key endpoints require JWT authentication. API keys are scoped to a builder and a specific realm.

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",
"builderId": "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 builder.

Response

json
{
"success": true,
"data": {
"apiKeys": [
{
"id": "388ffe67-...",
"builderId": "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
}
}

Arca Object Endpoints

All explorer endpoints require authentication (JWT or API key). Objects are scoped to a realm and a builder.

Create Arca Object

POST/api/v1/objectsJWT / API Key

Create an Arca object. This endpoint is idempotent: if an active object already exists at the path with matching type and denomination, it is returned without error. Atomically creates both the object and a create operation in a single transaction.

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. A type or denomination mismatch returns an error.

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.
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 200 OK

json
{
"success": true,
"data": {
"object": {
"id": "a1b2c3d4-...",
"realmId": "6d25623e-...",
"path": "/treasury/usd-reserve",
"type": "denominated",
"denomination": "USD",
"status": "active",
"metadata": null,
"createdAt": "2026-02-11T21:12:52.529Z",
"updatedAt": "2026-02-11T21:12:52.529Z"
},
"operation": {
"id": "e5f6a7b8-...",
"realmId": "6d25623e-...",
"path": "/op/create/treasury/usd-reserve:1",
"type": "create",
"state": "completed",
"sourceArcaPath": null,
"targetArcaPath": "/treasury/usd-reserve",
"input": "...",
"outcome": "...",
"actorType": "builder",
"actorId": "b1234...",
"createdAt": "2026-02-11T21:12:52.529Z",
"updatedAt": "2026-02-11T21:12:52.529Z"
}
}
}

List Arca Objects

GET/api/v1/objectsJWT / API Key

List objects in a realm. Optionally filter by path prefix.

Query Parameters

realmIdstringrequired
Realm ID.
prefixstring
Path prefix to filter (e.g., /treasury).
includeDeletedboolean
Include deleted and deleting objects. Defaults to false.

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.

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": {
"prefix": "/treasury/",
"folders": ["reserves/"],
"objects": [
{ "id": "...", "path": "/treasury/usd-reserve", ... }
]
}
}

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

Reserved balances (in-flight amounts tied to pending operations) are included in totalEquityUsd since they represent value the user still owns. The totalReservedUsd field breaks out how much of the total is currently in-flight.

Response

json
{
"success": true,
"data": {
"prefix": "/users/u-123/",
"totalEquityUsd": "15734.56",
"totalReservedUsd": "500",
"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-PERP", "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 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 emits aggregation.invalidated SSE 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",
"totalReservedUsd": "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,
"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": [
{
"id": "b1c2d3e4-...",
"arcaId": "a1b2c3d4-...",
"denomination": "USD",
"amount": "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-PERP", "side": "LONG", "size": "0.25", "leverage": 3 }]
}
}

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",
"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. This endpoint returns all of them sorted newest-first.

Path Parameters

idstringrequired
Object ID (UUID) of any version.

Response

json
{
"success": true,
"data": {
"versions": [
{ "id": "...", "path": "/treasury/usd-reserve", "status": "active", "deletedAt": null, ... },
{ "id": "...", "path": "/treasury/usd-reserve", "status": "deleted", "deletedAt": "2026-02-10T15:00:00.000Z", ... }
]
}
}

Operation Endpoints

Create Operation

POST/api/v1/operationsJWT / API Key

Create a new operation against Arca objects. Operations track intent and state transitions.

Request Body

realmIdstringrequired
Realm ID.
pathstringrequired
Arca path the operation targets.
typestringrequired
One of "transfer", "create", "delete", "deposit", "withdrawal", "swap", "order", "cancel".
sourceArcaPathstring
Source object path (for transfers/swaps).
targetArcaPathstring
Target object path (for transfers/swaps).
inputstring
Optional JSON input payload.

Response 201 Created

json
{
"success": true,
"data": {
"id": "e5f6a7b8-...",
"realmId": "6d25623e-...",
"path": "/treasury/usd-reserve",
"type": "deposit",
"state": "pending",
"sourceArcaPath": null,
"targetArcaPath": "/treasury/usd-reserve",
"input": null,
"outcome": null,
"actorType": "builder",
"actorId": "b1234...",
"createdAt": "2026-02-11T21:12:52.529Z",
"updatedAt": "2026-02-11T21:12:52.529Z"
}
}

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 operation type (e.g., "deposit").

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.

Path Parameters

idstringrequired
Operation ID (UUID).

Response

json
{
"success": true,
"data": {
"operation": {
"id": "e5f6a7b8-...",
"type": "deposit",
"state": "completed",
"actorType": "api_key",
"actorId": "k-9876...",
...
},
"events": [
{ "id": "...", "type": "deposit.completed", ... }
],
"deltas": [
{ "id": "...", "deltaType": "balance_change", "beforeValue": "0", "afterValue": "1000.00", ... }
]
}
}

Event Endpoints

Create Event

POST/api/v1/eventsJWT / API Key

Create a new event in a realm.

Request Body

realmIdstringrequired
Realm ID.
typestringrequired
Event type (e.g., "deposit.completed").
operationIdstring
Associated operation ID.
arcaPathstring
Arca object path this event relates to.
pathstring
Optional path for the event.
payloadstring
Optional JSON payload.

Response 201 Created

json
{
"success": true,
"data": {
"id": "f1e2d3c4-...",
"realmId": "6d25623e-...",
"operationId": "e5f6a7b8-...",
"arcaPath": "/treasury/usd-reserve",
"type": "deposit.completed",
"path": null,
"payload": "{\"amount\":\"1000.00\"}",
"createdAt": "2026-02-11T21:12:52.529Z"
}
}

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", ... }
]
}
}

Deposit Endpoints

Initiate Deposit

POST/api/v1/depositJWT / API Key

Initiate a deposit to a denominated Arca object. This creates a pending deposit operation, then starts a Temporal workflow with a durable timer. In demo realms the deposit simulates asynchronously and completes (or fails) after the specified duration. On completion, a deposit.completed event is emitted with the balance_change delta. The durable timer survives service restarts — deposits are guaranteed to settle.

Request Body

realmIdstringrequired
Realm ID.
arcaPathstringrequired
Path of the target Arca object.
amountstringrequired
Deposit 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.
durationSecondsnumber
Simulated processing time in seconds. Default: 5.
willSucceedboolean
Whether the simulated deposit should succeed. Default: true.

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 deposit 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 deposit amount is determined by what arrives on-chain, not by what the user 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., "anvil" for demo, "arbitrum" for production).
expiresAtstring
Deposit intent expiration timestamp. The on-chain transfer must arrive before this time.

On-Chain Deposit Flow

  1. Call POST /deposit to create a deposit 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: Simulate On-Chain Send

In demo realms (using the Anvil local chain), you can simulate an on-chain USDC transfer without a real wallet. The portal Deposit modal includes a Simulate On-Chain Send button that calls the internal endpoint below.

POST/api/v1/internal/custody-pool/simulate-depositInternal
Request Body
realmIdstring
The realm to simulate the deposit for.
amountstring
Amount of mock USDC to mint and send to the pool.
Response
poolAddressstring
The pool address tokens were sent to.
amountstring
Amount minted.
txHashstring
On-chain transaction hash for the mint.
chainstring
Chain identifier (e.g., "anvil").

Transfer Endpoints

Execute Transfer

POST/api/v1/transferJWT / API Key

Atomically transfer balance between two denominated Arca objects. Both objects must share the same denomination and the source must have sufficient funds. The entire operation — balance reads, debit, credit, operation creation, event, and state deltas — is executed in a single Spanner read-write transaction.

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, objects are not denominated, or operation path is blank.
404NOT_FOUND
Source or target Arca object not found at the given path, or realm not found.

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.

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",
"createdAt": "...",
"updatedAt": "..."
}

Status values: held (in-flight), released (settled),cancelled (operation failed).

Exchange Account State

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

Returns account state including balance, margin summary, positions, and open orders.

Update Leverage

json
POST /api/v1/objects/{id}/exchange/leverage
{
"coin": "BTC",
"leverage": 10
}

Set the leverage for a coin. 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=BTC # Single coin

Place Order (Operation)

json
POST /api/v1/objects/{id}/exchange/orders
{
"realmId": "...",
"path": "/op/order/btc-buy-1",
"coin": "BTC",
"side": "BUY",
"orderType": "MARKET",
"size": "0.001"
}

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. Orders use the leverage currently set for the coin (via the Update Leverage endpoint above).

Supported fields: coin, side (BUY/SELL),orderType (MARKET/LIMIT), size, price(required for LIMIT), 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).

List Orders

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

Cancel Order

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

List Positions

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

Market Data (Global)

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/book/{coin} # L2 order book

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.
  • Auto-named objects — use a trailing-slash prefix (e.g., /wallets/) to generate sequential child paths.

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.createDenominatedArca({
ref: '/wallets/main',
denomination: 'USD',
operationPath: opPath,
});
// Safe to retry — same operationPath returns the same result.

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

Event Stream (SSE)

GET/api/v1/streamToken (query param)

Subscribe to a real-time stream of realm events via Server-Sent Events. Because the browser EventSource API cannot send custom headers, the JWT token is passed as a query parameter instead of in the Authorization header.

Query Parameters

realmIdstringrequired
Realm ID to stream events for.
tokenstringrequired
JWT token for authentication.

Connection Example

javascript
const token = "eyJhbGciOiJIUzI1NiIs...";
const realmId = "6d25623e-...";
const url = `/api/v1/stream?realmId=${realmId}&token=${token}`;
const es = new EventSource(url);
es.addEventListener("connected", (e) => {
console.log("Connected:", JSON.parse(e.data));
});
// Listen for specific event types
es.addEventListener("deposit.completed", (e) => {
const event = JSON.parse(e.data);
console.log("Deposit completed:", event);
});
es.addEventListener("transfer.completed", (e) => {
const event = JSON.parse(e.data);
console.log("Transfer completed:", event);
});

Event Format

On connection, a connected event is sent as a heartbeat. Subsequent events match the event types created in the realm (e.g., deposit.completed, transfer.completed, order.filled). Each SSE event's data field contains the full serialized event object.

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 environment variable).

{ "data": { "enabled": true } }

Chat

POST/api/v1/agent/chatJWT / API Key

Send a conversation to the agent. Returns a text/event-stream (SSE) response with the following event types:

  • text — Streamed text content from the assistant
  • tool_use — Shows which tool the agent is calling (e.g. browsing objects, checking balances)
  • plan — A structured plan with executable steps
  • done — Stream complete

Request Body

{
"realmId": "uuid",
"messages": [
{ "role": "user", "content": "Transfer 500 from user-2/main to their exchange and open a 10x BTC long" }
]
}

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.

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" }