Skip to content

Core concepts

The handful of ideas that everything else in Photon builds on: the response envelope, the transaction lifecycle, how your balance and limits work, what costUSD means, the transaction types, sync versus async submission, and regions.

The envelope

Every Photon response uses one envelope. Success:

{
  "success": true,
  "data": { "txId": "68fa3450539a3c9d28bbca33" },
  "timestamp": "2026-07-29T09:14:03.412Z"
}

Errors carry a stable machine-readable code, a category, and a traceId you can quote to support:

{
  "success": false,
  "error": {
    "code": "TRANSACTION_002",
    "message": "Insufficient balance to process transaction",
    "category": "TRANSACTION",
    "details": { "required": 1.25, "available": 0.5 },
    "traceId": "a1b2c3d4e5f6"
  },
  "timestamp": "2026-07-29T09:14:03.412Z"
}

details is optional and code-specific. The full table lives in the error codes reference.

The transaction lifecycle

A transaction's status moves through five states, two of them terminal:

Status Meaning Terminal
NOT_PICKED_UP Accepted and queued; no worker has claimed it yet No
PENDING A relay worker is executing it No
NEEDS_TO_BE_RETRIED A recoverable failure occurred; waiting out a backoff before returning to PENDING No
EXECUTED Confirmed on chain — executionTxHash, costUSD, and latency are set Yes
FAILED Gave up — the record carries the error; revert details are available on request Yes

Each retry attempt increments the retries counter on the record, so you can see how hard Photon had to work. The overview draws the transition diagram.

Accounts and balances

Your account holds a prepaid USD balance, split into two numbers you can read from GET /api/user:

  • availableBalance — USD you can still spend.
  • pendingBalance — USD reserved for transactions that are queued or in flight. The reservation is the submission's estimate; it resolves into a real charge (or is released) when the transaction reaches a terminal state.

Three limits bound your spend, and each maps to a distinct error:

Limit Where it lives Error on breach
maxUSD Optional field on a single submission TRANSACTION_001
perTxLimit Your account's userConfig TRANSACTION_001
dailyLimit Your account's userConfig (consumed amount tracked as dailyAmountConsumed) TRANSACTION_006

A submission whose estimate exceeds availableBalance is rejected with TRANSACTION_002 before it ever queues.

costUSD

Photon charges in USD, not in gas tokens. You will meet two related fields: estimatedCostUSD, returned at submission time and used for the reservation, and costUSD, set on the record when the transaction executes — the amount actually debited. isAccountCharged on the record confirms the debit happened. Estimates and finals usually differ slightly, because gas prices move between queueing and mining. To see a price before committing, POST /api/quote returns costUSD for a transaction without submitting it.

Transaction types

The transactionType field selects how Photon relays. Four values exist:

Value What it means
flash The default: a standard sponsored transaction, signed and paid by Photon's relayer
flash-blocks Sub-second preconfirmation relaying — only on Base (8453) and Base Sepolia (84532), and incompatible with authorizationList. See Flash-blocks
authenticated A sponsored transaction bound to a wallet-authenticated user. See Authenticated transactions
funding-signed Photon funds the sender's address, then broadcasts a transaction they signed — used by POST /api/submit-signed-tx. See Funding-signed transactions

Any other value is rejected with VALIDATION_004.

Sync vs async submission

POST /api/submit-tx is fire-and-observe: you get a txId immediately and watch the lifecycle via polling, webhooks, or WebSocket. It suits high throughput and anything user-facing where you'd rather render "processing" than block.

POST /api/submit-tx-sync queues the same way but holds the request open until the transaction is terminal, up to a timeoutMs you choose (1000–120000 ms, default 30000). A completed transaction returns 200 with the full record; hitting the timeout returns 202 with the in-flight record and timedOut: true — the transaction keeps going, and you fall back to the async tools. Setting returnOnTxHash releases the call as soon as a transaction hash exists, trading finality for latency.

Regions

Photon relays from multiple geographic regions, each with its own relayer identity and node connectivity. Every submission is routed to a region — chosen automatically from where the request comes from, or explicitly via the X-Edith-Region header or the region field on the request body (the header wins; an inactive region name is ignored). The record's submitRegion field tells you where a transaction was relayed from. Region routing explains the selection rules and when pinning a region is worth it.