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.