apps/api/test/last-mile.test.ts — the real router, the real saga, the real ledger, the real chain simulator.
Onboarding
Sandbox credentials are issued on registration, before any paperwork. A partner should be able to integrate on day one; making them wait on KYB to see a single API response is how integrations stall. Live access requires all of:
The last two are the ones usually skipped, and they are exactly the ones that predict a bad go-live.
goLive() refuses while anything is outstanding and names what is missing.
Credentials are prefixed by environment — ak_test_ / sk_test_ versus ak_live_ / sk_live_ — so a key pasted into the wrong config is visibly wrong rather than quietly wrong.
Sandbox
Isolated per partner, and resettable. Reset is the feature partners use most: integration means running the same scenario until it is right, and that only works if the slate is genuinely clean. Each reset bumps a generation counter, so a partner can prove which one they are on.Magic amounts
A partner cannot make Arc’s rails fail on demand. The sandbox reads the last two minor units of the send amount as an instruction:
So
€1000.66 always produces a rail rejection.
Why not a simulate: true flag? Because then the failure path runs through a code path production never takes, and the thing you tested is not the thing that ships. Magic amounts keep the request shape byte-identical to live.
Every triggered failure still compensates: the sandbox tests assert the ledger balances after a forced rail rejection, compliance block, and stuck settlement.
The Last Mile API
Five routes, deliberately few:Authentication
Every request carriesarc-client-id, arc-timestamp, arc-nonce and arc-signature. The signature is HMAC-SHA256 over:
Amounts on the wire
Idempotency
Idempotency-Key on a transfer means a retry returns the original transfer instead of creating a second. The test asserts both the same id and that usage metering recorded exactly one transfer — a weaker test would pass while double-charging.
The SDK
@arc/sdk handles signing, nonces, retries and error typing.
ArcApiError.retryable is the field integrators need: a 429 or 5xx may succeed on retry, a 409 or 401 will not. Retrying a non-retryable error is how partners generate support tickets.
verifyWebhookSignature is exported deliberately, so integrators verify with the same code Arc signs with. The most common webhook integration bug is a receiver that “verifies” incorrectly and accepts anything.
Billing
Rev-share rebates a share of the corridor revenue Arc earned on that partner’s traffic — it aligns the partner with volume rather than just charging for it.
An invoice is money, so it obeys the same rules as a transfer: integer minor units, exact arithmetic, and
invoiceJournalEntries produces balanced ledger entries rather than leaving billing in a spreadsheet beside the ledger.
What is not here yet
- No HTTP server.
createApireturns ahandle(request)function and the SDK talks to it through aTransportseam. Wiring Fastify around it is mechanical; nothing about the routing, auth or idempotency would change. - Generated OpenAPI. The route shapes are hand-written here rather than emitted from the Zod schemas.
- Partner state is in-memory. Partners, credentials and usage records do not survive a restart, unlike the ledger and outbox.
- Sandbox reset does not clear the ledger. It clears quotes and transfers; ledger accounts persist.
- No per-partner rate limit tiers. One limiter configuration for everyone.