API reference
The Splitjar API is a single Cloudflare Worker exposing JSON over HTTPS. Every route lives under https://splitjar.app/api/.
Authentication
Two authentication modes:
- Cookie session (
sj_session): set by the magic-link flow. Used by the SPA atsplitjar.app/app/*. - Reply-edit token (HMAC, body-embedded): used by the email reply path only. Not a general-purpose API token.
There is no public bearer-token API at this stage. If you need programmatic access, contact hello@splitjar.app.
Rate limits
Two layers stack in front of every route:
| Layer | Scope | Where |
|---|---|---|
| Cloudflare WAF | Per IP | Edge — see scripts/cf-rate-limits.mjs |
| In-app KV | Per email | Worker — applied per-route in src/lib/auth.ts |
The WAF is volumetric (per-IP, coarse). The KV is precise (per-email, intentional). Both can fire on the same request.
Auth & sessions
| Method | Path | Purpose |
|---|---|---|
POST | /api/auth/start | Atomic: upsert user → session → group → invites → magic link. Returns { group: { slug } }. |
POST | /api/auth/magic-link | Send a sign-in link to an existing user. |
GET | /api/auth/callback?token=… | Verify magic-link, set sj_session cookie, flip verified_at. |
POST | /api/auth/signout | Clear sj_session. |
GET | /api/me | Current user’s profile + entitlement view. |
PATCH | /api/me | Update display name, locale. |
Jars (groups)
| Method | Path | Purpose |
|---|---|---|
POST | /api/groups | Create a jar. |
GET | /api/groups/:idOrSlug | Read jar with members + entitlement. |
PATCH | /api/groups/:idOrSlug | Owner-only: rename, change default split, change base currency. |
POST | /api/groups/:idOrSlug/members | Owner-only: add a member by email. |
PATCH | /api/groups/:idOrSlug/members/:userId | Owner-only: rename a member (subject to the cross-tenant guard). |
DELETE | /api/groups/:idOrSlug/members/:userId | Owner-only: remove a member. 409 if they have ledger artifacts. |
Receipts
| Method | Path | Purpose |
|---|---|---|
POST | /api/ingest | Web upload of a receipt (multipart). |
GET | /api/groups/:idOrSlug/receipts | List receipts in the jar. |
GET | /api/receipts/:id | Read one receipt with extraction + splits. |
PATCH | /api/receipts/:id | Edit fields. Re-enqueues fx + finalise. |
POST | /api/receipts/:id/approve | Move out of needs_review. |
DELETE | /api/receipts/:id | Soft-delete (status flip, ledger preserved). |
Billing
| Method | Path | Purpose |
|---|---|---|
POST | /api/billing/checkout | Stripe Checkout session for Jar or Year. |
POST | /api/billing/portal | Stripe Customer Portal link. |
POST | /api/billing/webhook | Stripe webhook ingress (signature-verified). |
Errors
All errors share the shape:
{ "error": { "code": "forbidden_personalised_name", "message": "This member has set their own display name. Only they can change it.", "details": {} }}The code is stable and machine-readable. The message is locale-resolved per the recipient’s users.locale.
Conventions
- All times are Unix milliseconds.
- All amounts are numbers (not strings), denominated as the receipt’s
currencyfor the original and the jar’sbase_currencyfor the converted figure. - Slugs are 6 characters of base32, prefix-free with the jar id.