REST Endpoints
The Foir public API is GraphQL-first, but a handful of surfaces are plain REST because their callers aren’t GraphQL clients — a mail client following an unsubscribe link, Stripe POSTing a billing event, or an off-the-shelf OIDC library discovering the customer login lane. They all live on the same public host, https://api.foir.dev.
| Endpoint | Method | Auth | Purpose |
|---|---|---|---|
/v1/email/unsubscribe | GET, POST | Signed token (query string) | RFC 8058 one-click unsubscribe from notification emails |
/v1/webhooks/stripe | POST | Stripe-Signature header | Stripe → Foir billing webhook receiver (operated by Foir) |
/customer/.well-known/openid-configuration | GET | none | OIDC discovery document for the customer login lane |
/customer/oauth/authorize, /token, /revoke | GET, POST | PKCE / client | OAuth 2.1 authorization-code flow (see the Login with Foir guide) |
/.well-known/jwks.json | GET | none | Public keys for verifying customer access tokens |
The internal admin and CLI auth endpoints (operator login, auth/cli/*) are not part of the public API — they live on Foir’s internal host and aren’t reachable here.
Email unsubscribe (RFC 8058)
Every notification email Foir sends carries an RFC 8058 List-Unsubscribe header pointing at:
https://api.foir.dev/v1/email/unsubscribe?token=<signed-token>The token is a self-contained, HMAC-signed value Foir mints when the email is queued; it identifies the recipient and is the only credential. There is nothing for you to call or configure — the endpoint exists so a recipient’s mail client (or the “unsubscribe” link in the email) can opt them out of notification emails in one click.
| Method | Behavior |
|---|---|
GET | Renders a confirmation page. Prefetch-safe — it never changes state, so a mail client or security scanner that prefetches the link can’t silently unsubscribe anyone. |
POST | Performs the opt-out. This is the RFC 8058 one-click target (mail clients POST it directly) and the confirmation page’s button. |
A successful opt-out records a global opt-out from notification emails for that recipient. Account and security emails are unaffected.
Responses: 200 with an HTML page on success; 400 with an HTML page when the token is missing, invalid, or expired.
Stripe webhook
POST https://api.foir.dev/v1/webhooks/stripeFoir’s billing receiver for Stripe webhook events (subscription lifecycle, invoice paid / payment failed). It is operated by Foir — API consumers don’t call it; it’s listed here for completeness of the public surface.
Authentication is the Stripe-Signature header, verified server-side against the raw request body (Stripe signs the raw bytes), so the signature is the only credential and no secret is exposed at the edge.
Responses: 2xx once the event is accepted; 400 for a missing or unverifiable signature; 503 while the receiver is unconfigured (so Stripe retries).
Customer authentication (OIDC)
The customer hosted-login lane — Login with Foir — is a standard OAuth 2.1 / OIDC authorization server. Most apps integrate it through the two GraphQL mutations described in the Login with Foir guide; you generally don’t call these REST endpoints directly.
If you’re integrating from a generic OIDC library (Auth0 SDK, Spring Security, etc.), point it at the discovery document and let it auto-configure:
https://api.foir.dev/customer/.well-known/openid-configurationIt advertises the lane’s endpoints:
| Endpoint | Method | Purpose |
|---|---|---|
/customer/oauth/authorize | GET | Authorization-code request (PKCE S256 required). Bounces the customer into the hosted login UI. |
/customer/oauth/token | POST | Exchange an authorization code for tokens (grant_type=authorization_code) or rotate a refresh token (grant_type=refresh_token). Form-encoded, per RFC 6749. |
/customer/oauth/revoke | POST | Revoke a refresh token and its rotation family (RFC 7009 ). |
/.well-known/jwks.json | GET | Public keys for verifying customer access tokens. |
Access tokens are 15-minute EdDSA JWTs (issuer foir-customer); refresh tokens are 30-day opaque strings, rotated on every use with replay detection. You don’t need to verify tokens yourself — the platform does — but a backend-for-frontend that wants to can fetch the keys from the JWKS URL above. See Authentication for using customer tokens against the GraphQL API.