API reference
The Splitjar API is a single Cloudflare Worker exposing JSON over HTTPS. Every route lives under https://splitjar.app/api/.
Authentication
Section titled “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
Section titled “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
Section titled “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)
Section titled “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
Section titled “Receipts”| Method | Path | Purpose |
|---|---|---|
POST | /api/ingest | Web upload of a receipt (multipart). |
GET | /api/receipts/by-group/:idOrSlug | List receipts in the jar. |
GET | /api/receipts/by-group/:idOrSlug/map-points | Geocoded points for the per-jar map. |
GET | /api/receipts/:id | Read one receipt with extraction + splits. |
PATCH | /api/receipts/:id | Edit fields. Re-enqueues fx + finalise. |
PATCH | /api/receipts/:id/line-items | Replace the per-item assignment map. |
POST | /api/receipts/:id/translate | Run a full English translation of every line. |
POST | /api/receipts/:id/confirm-duplicate | ”Add anyway” on a held duplicate. |
GET | /api/receipts/:id/image | Proxy the R2 attachment for the detail page. |
DELETE | /api/receipts/:id | Hard-delete. Owner or purchaser only. Removes the row, splits, and R2 image; balances re-aggregate from what’s left. |
Billing
Section titled “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
Section titled “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
Section titled “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.