Backend for radio.com/all-access. JSON over HTTPS. Auth via Keycloak. Billing via Stripe. Trebel Max receives a daily SFTP file.
Keycloak holds the user and the allaccess_enddate attribute. Stripe is what flips entitlement on and off. When a webhook lands we update that attribute. SeatClub reads the flag at SSO. Trebel gets a daily SFTP file of active emails (backend-only, no frontend endpoint).
We're just the glue. No DB of our own.
The frontend logs into Keycloak directly with keycloak-js, then calls this API with the access token:
Authorization: Bearer <keycloak-access-token>
We verify against Keycloak's JWKS. No roundtrip on every request.
Liveness check. Returns {"status": "ok"}.
Readiness check.
Creates a Keycloak user, sends the verification email, creates a Stripe customer, and returns an Embedded Checkout client_secret.
{
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada@example.com",
"password": "<min 8 chars>"
}
{
"user_id": "f3a1...",
"client_secret": "cs_test_..._secret_..."
}
Frontend mounts Stripe's Embedded Checkout with this client_secret.
Resends the Keycloak verification email. Returns {"ok": true} either way. If we 404'd on unknown emails it'd leak which addresses are members. Rate-limited 5/hr per IP.
{ "email": "ada@example.com" }
Kicks off Keycloak's "update password" required action and emails the user a link. Same silent-on-unknown-email behavior + rate limit as above.
{ "email": "ada@example.com" }
Profile + entitlements for the calling user.
{
"id": "f3a1...",
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"member_since": "2026-05-06",
"end_date": "2027-05-05",
"total_orders": 1,
"allaccess": true,
"seatclub": true,
"trebel_max": true
}
Update first/last name. Returns the same shape as GET /me.
{ "first_name": "Ada", "last_name": "Lovelace" }
Just the entitlement flags. Useful when the SeatClub white-label or any other integrator wants the bare check.
{
"end_date": "2027-05-05",
"allaccess": true,
"seatclub": true,
"trebel_max": true
}
Returns a fresh Stripe Embedded Checkout client_secret. Used when the user is signed in but hasn't finished paying.
{ "client_secret": "cs_test_..._secret_..." }
Returns a Stripe Customer Portal URL. Drives the "Manage Plan" button. Stripe doesn't offer an embedded portal so this stays a redirect.
{ "url": "https://billing.stripe.com/p/session/..." }
Subscription details for the Settings page (status, next bill date, price). Returns {"subscription": null} if the user hasn't paid yet.
{
"subscription": {
"id": "sub_...",
"status": "active",
"current_period_end": 1773926400,
"cancel_at_period_end": false,
"amount": 14900,
"currency": "usd",
"interval": "year"
}
}
Recent invoices for the order history page.
{
"orders": [
{
"id": "in_...", "number": "AA-0001",
"amount_paid": 14900, "currency": "usd",
"status": "paid", "created": 1741200000,
"hosted_invoice_url": "https://...", "invoice_pdf": "https://..."
}
]
}
Stripe-signed webhook. Idempotent. Handled events:
checkout.session.completed - link Stripe customer + subscription, set allaccess_enddateinvoice.paid / invoice.payment_succeeded - bump allaccess_enddateinvoice.payment_failed - log only, don't revokecustomer.subscription.deleted - log. Entitlement expires naturallyAll errors share one shape:
{
"code": "AA-AUTH-002",
"message": "Token expired",
"request_id": "..."
}
| Code | Meaning |
|---|---|
AA-AUTH-001 | Token invalid |
AA-AUTH-002 | Token expired |
AA-AUTH-003 | Signature mismatch |
AA-AUTH-004 | Audience mismatch |
AA-ENT-001 | No active membership |
AA-ENT-002 | Membership expired |
AA-USER-001 | User not found |
AA-USER-002 | User mismatch |
AA-PAY-001 | Stripe customer create failed |
AA-PAY-002 | Webhook signature invalid |
AA-TRBL-001 | SFTP upload failed |
Stuff we left off on purpose: admin/support endpoints (manual entitlement grants, user lookup, webhook replay), deep health checks that ping downstreams, version endpoint, Sentry, GDPR data export, server-side promo code validation. Each touches a call Bryan or product owns. Easy to add later if a real need shows up.
Real Keycloak and Stripe aren't required to try the API. Open the playground to step through signup, payment, entitlements, and Trebel handoff against a sandboxed in-memory copy at /demo/*.
Source: allaccess-backend. Plan: ../GAMEPLAN.md.