AUD virtual accounts are live. Collect from Australian bank transfers, settle in USDC.See how →
locals
Products
Virtual accounts Payouts Cards
Platform
Coverage Developers Company Talk to us
Home/Developers
v1Sandbox with simulated deposits

Four resources. One status model.

Customers, routes, quotes and transfers. Everything else is a field on one of them. Build against sandbox, switch the key, go live.

Idempotent writes Signed webhooks JSON in, JSON out
~/harbour-app — zsh sandbox
$ curl https://api.locals.money/v1/routes \
    -H "Authorization: Bearer $LOCALS_KEY" \
    -H "Idempotency-Key: 6f1c0b9e-41d2-4c1a-9e2a-3b7d5e2a4c10" \
    -d '{"customerId":"cus_8Hq2Lm","source":{"currency":"AUD"},
        "destination":{"currency":"USDC","chain":"polygon"}}'
HTTP/1.1 201 Createdroute_3f9a
{
  "id": "route_3f9a",
  "status": "pending",
  "customerId": "cus_8Hq2Lm",
  "source": { "currency": "AUD" },
  "destination": { "currency": "USDC", "chain": "polygon" },
  "depositInstructions": {
    "accountName": "Harbour & Co",
    "routingNumber": "000-000",
    "accountNumber": "12345678"
  }
}
POST you.example/hooks200
route.activated
data.idroute_3f9a
Locals-Signature verified
route.deposit.receivedA$12,500.00 · ref HC-2210
8,137.50 USDC
Base URLhttps://api.locals.money/v1
AuthAuthorization: Bearer …
Safe retriesIdempotency-Key
FormatJSON · camelCase
Quickstart

From zero to first deposit.

Sandbox has the full API, including a deposit simulator. Nothing you do there moves real money.

terminal 1 / 6
# Sandbox and live keys are separate. Keep them on your server.
export LOCALS_KEY="<your sandbox key>"

# Check the key: list your customers
curl https://api.locals.money/v1/customers \
  -H "Authorization: Bearer $LOCALS_KEY"
curl https://api.locals.money/v1/customers \
  -H "Authorization: Bearer $LOCALS_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "type": "business", "name": "Harbour & Co" }'

# 201 Created
{ "id": "cus_8Hq2Lm", "verification": { "status": "in_progress" } }
curl https://api.locals.money/v1/routes \
  -H "Authorization: Bearer $LOCALS_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "customerId": "cus_8Hq2Lm",
        "source": { "currency": "AUD" },
        "destination": { "currency": "USDC", "chain": "polygon" } }'

# 201 Created: show depositInstructions to your customer
{ "id": "route_3f9a", "status": "pending", "depositInstructions": { … } }
curl https://api.locals.money/v1/test/routes/route_3f9a/simulate \
  -H "Authorization: Bearer $LOCALS_KEY" \
  -d '{ "amount": "12500.00", "currency": "AUD" }'

# Then, on your webhook endpoint:
route.deposit.received  A$12,500.00
transfer.processing     trf_91kd
transfer.completed      8,137.50 USDC
app.post('/hooks', express.raw({ type: '*/*' }), async (req, res) => {
  if (!verify(req.body, req.get('Locals-Signature'), SECRET)) return res.sendStatus(400);
  const event = JSON.parse(req.body);
  // Retries reuse the same id: processing twice is safe
  if (await seen(event.id)) return res.sendStatus(200);
  await ledger.apply(event);
  res.sendStatus(200);
});
# .env
- LOCALS_KEY="<your sandbox key>"
+ LOCALS_KEY="<your live key>"

# Same endpoints, same payloads, real rails
curl https://api.locals.money/v1/routes/route_3f9a \
  -H "Authorization: Bearer $LOCALS_KEY"
No real money in sandboxsandbox
API reference

Every endpoint on one page.

Base URL https://api.locals.money/v1. JSON in, JSON out. Every write accepts an Idempotency-Key.

Customerscus_…

POST/v1/customersCreate an individual or business customer with their verification.idempotent
GET/v1/customersList your customers.
GET/v1/customers/{id}Customer and verification status.

Routesroute_…

POST/v1/routesOpen a virtual account: currency in, stablecoin out.idempotent
GET/v1/routesList a customer's routes.
GET/v1/routes/{id}Route, status and deposit instructions. GET/v1/routes/{id}/reconciliationEvery deposit on the route, matched to its settlement.
POST/v1/routes/{id}/whitelistAllow-list the account a customer pays from, where the market requires it.idempotent

Quotes and transfersquo_… trf_…

POST/v1/quotesPrice a payout. Quotes expire.idempotent POST/v1/transfersAccept a quote and get exact funding instructions.idempotent
GET/v1/transfersList transfers.
GET/v1/transfers/{id}Transfer and its status history.

Cardsillustrative

POST/v1/cardsIssue a card to an eligible customer. Shape shown for illustration.preview
EVENTcard.authorizationAn authorisation against the stablecoin balance, in real time.preview
Try a request

Pick an endpoint. Copy the call.

Every example runs against sandbox with your key. The response shows the fields you will build on.

1curl -X POST https://api.locals.money/v1/routes \
2  -H "Authorization: Bearer $LOCALS_KEY" \
3  -H "Idempotency-Key: $(uuidgen)" \
4  -d '{ "customerId": "cus_8Hq2Lm", "source": { "currency": "AUD" }, "destination": { "currency": "USDC", "chain": "polygon" } }'
POST /v1/routes · sandbox201 Created
201Responseroute_3f9a
{
  "id": "route_3f9a",
  "status": "pending"
}
Webhooks

Know the moment money moves.

Each event is signed and carries an id. Verify, store, then act. Retries use the same id, so processing twice is safe.

Customerverification
in_progress customer.verification.updated approved requires_action rejected
Routeroute_3f9a
pending route.activated active route.deposit.receivedon every deposit
Transfertrf_91kd
pending transfer.processing processing transfer.completed · failed complete failed
import { createHmac, timingSafeEqual } from 'crypto';

// Body must be the raw request bytes
export function verify(rawBody, header, secret) {
  const [ts, sig] = header.split(',');
  const expected = createHmac('sha256', secret)
    .update(`${ts}.${rawBody}`)
    .digest();
  return timingSafeEqual(expected, Buffer.from(sig, 'hex'));
}
import hashlib, hmac

# raw_body must be the raw request bytes
def verify(raw_body: bytes, header: str, secret: str) -> bool:
    ts, sig = header.split(",")
    expected = hmac.new(secret.encode(), ts.encode() + b"." + raw_body,
                        hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)
signature headerLocals-Signature
Deliverieshttps://you.example/hooks
    • Verify the signatureOver the raw body, before you parse it or credit anything.
    • Store the event idEvents can arrive more than once. Retries carry the same id.
    • Then actUpdate your ledger and answer 200.
    Errors

    Errors that tell you what to do.

    Every error has a stable code, a message for humans and, on validation errors, the fields that failed.

    422ResponsePOST /v1/routes
    {
      "statusCode": 422,
      "code": "currency_not_enabled",
      "message": "AED accounts are not enabled for this customer"
    }
    Sandbox

    Every status. No real money.

    Sandbox keys give you the full API. Send a test bank transfer to a route and watch it arrive, convert and settle, with every webhook along the way.

    • Simulated bank transfersOne call on any sandbox route.
    • Real webhooks, signedThe same events and payloads as production.
    • Same code in productionSwap the key. Nothing else changes.
    Harbour & Coroute_3f9a · AUD → USDC
    Active
    Amount
    POST /v1/test/routes/route_3f9a/simulate { "amount": "12500.00" }
    • route.deposit.received09:41:07 · ref TEST-0001
      A$12,500.00
    • transfer.processingtrf_91kd · 1 AUD = 0.6510 USDC
      8,137.50 USDC
    • transfer.completedtx 0x5e0c…a7d4
      Completed
    Changelog

    What changed, and when.

    Breaking changes ship as a new version. Additions land in v1 and are listed here, together with the network updates behind accounts, payouts and cards, marked Network.

    1. AddedPayouts

      One transfer per quote.

      Retrying a transfer with the same quote returns the transfer already created, also after a timeout or a lost response. A quote reused for another customer or payout account is refused with 409.

      POST /v1/transfers
    2. LiveAccounts

      AUD virtual accounts live.

      Routes with source AUD run in production. Australian accounts accept deposits from one allow-listed sending account per route; money from any other account is returned to the sender.

      POST /v1/routes · POST /v1/routes/{id}/whitelist
    3. AddedAccounts

      Customers from an existing verification.

      Verified individuals become customers without repeating their checks. In the first production cases, approval came back in under a minute.

      POST /v1/customers
    4. AddedPayouts

      Quoted transfers.

      New kind: "quoted" transfers for payouts: quote, accept, fund the exact amount before it expires. Deposits and payouts share one status model.

      POST /v1/quotes · POST /v1/transfers
    5. NetworkCards

      Real-time card funding (beta).

      Cards can spend straight from the cardholder’s wallet: only the amount of each purchase is committed when it is authorised. No separate card balance to fund or top up, and the approval can be revoked at any time.

    6. NetworkCards

      Card programmes in Korean won.

      Card programmes can be denominated in won, backed by a won stablecoin, on the same issuing infrastructure.

    7. NetworkCards

      Scoped cards.

      Rules set before a card is issued: a limit per transaction, allowed merchants or categories, and an expiry. A card can close after one purchase, run until a fixed budget is spent, or renew with a recurring budget.

    8. NetworkCards

      Rewards on card spend.

      A points programme under your brand: you set the earning rules and merchant campaigns, cardholders redeem inside your app.

    9. NetworkAccounts

      EUR and GBP rails.

      The banking network now converts EUR and GBP to and from stablecoins, and sends and receives USD worldwide over SWIFT. It is the groundwork for EUR and GBP accounts, coming soon.

    10. NetworkCards

      Spend controls for AI agents (beta).

      Per-agent limits on amounts, merchants, categories and time windows, enforced before a transaction is processed, plus programme-wide caps on active cards and total spend.

    11. NetworkPayouts

      USDT across more than 20 chains.

      USDT now moves natively across more than 20 chains, without wrapped tokens or bridges, in both collections and payouts.

    12. NetworkPayouts

      Dedicated settlement for Asian corridors.

      A dedicated settlement partner backs the USDT corridors across Asia, adding liquidity and redundancy so payouts keep settling through high-volume and volatile periods.

    13. NetworkPayouts

      Institutional banking rails in Asia.

      Conversions between local currencies and stablecoins in key Asian markets can run on institutional banking infrastructure, not only on USDT liquidity.

    14. NetworkPayouts

      Liquidity for fiat and USDT corridors.

      A dedicated liquidity layer serves the fiat-to-USDT corridors, focused on Asia: high-volume payouts settle faster and conversions keep running when markets are volatile.

    Go local in every market you serve.

    Tell us where your customers are. We will tell you what is live, what is next, and how fast you can launch.