API overview
The GrinGo API is a single Hono app running on Cloudflare Workers. Every route is versionless — there is no /v1 prefix. Auth is a session JWT signed by us, issued after a native OAuth exchange with Apple or Google.
Base URLs
| Environment | URL |
|---|---|
| Local dev | http://localhost:8787 |
| Production | Set by Cloudflare Workers deploy — TBD DNS |
Auth
Most routes require:
Authorization: Bearer <session JWT>
The session JWT is obtained by POST /auth/session — exchange an Apple / Google ID token for a session. See architecture/auth for the full model. The token is HS256-signed with SESSION_SECRET, valid for 30 days by default.
Public endpoints (no session required): GET /health, POST /auth/session, POST /webhooks/infinia/:secret.
Content type
- Requests:
application/json(Zod-validated via@hono/zod-validator). - Responses:
application/json.
Errors
4xx— validation, auth, business rules. Body:{ "error": "<user-facing message>" }. Zod validation errors return400withzod-validator's default shape.5xx— internal / upstream. Body:{ "error": "<message>" }.- Infinia upstream failures surface as
502with a generic message; details are logged server-side.
Rate limiting
Not implemented at the API layer for MVP (see roadmap Phase 1.5). Cloudflare's default DDoS protection applies.
Idempotency
Where relevant (payment creation, withdraws), the API derives Infinia idempotency handles deterministically from the internal payment id — a retried API call returns the original resource instead of creating a duplicate. See payment-providers → Idempotency.
Route index
Full request/response shapes in endpoints. Webhook contract in webhooks.
What changed from the previous architecture
If you're familiar with the old API surface, the material differences:
- Auth is now
POST /auth/session { provider, idToken }returning our own session JWT — not a Privy access token verified against Privy's JWKS. - No
POST /virtual-accountsendpoint — the user's home-currency account is auto-provisioned at KYC completion. The funding instructions are surfaced viaGET /me. - No
POST /transactions/:id/confirm— there's no on-chain leg to confirm. PIX and withdraws advance via Infinia webhooks alone. POST /pix/quoteis a new step — locks the FX rate for 60s so the review screen shows a firm price.- Bank details columns for sensitive fields (
accountNumber,sortCode,iban,bic) are stored encrypted; the API returns masked values (****1234) except during in-app edit with an explicit reveal. POST /webhooks/infinia/:secretreplacesPOST /webhooks/bridge— the:secretpath segment is the first-line defence; verification is behind a swappable strategy.- New endpoints for data subject requests (GDPR).