POST /api/v1/admin/invites/:invite_id/accept
This page is auto-generated from
src/admin/routes.ts. Do not edit by hand — seedocs/dev/docs-auto-sync.md.
:warning: Heuristic extraction — not a contract. Response shapes below are mined from
c.json(...)literals in the handler via an AST walker. Computed fields, ternaries, and non-literal returns are shown as placeholders. Refer to the linked source file for the authoritative behaviour.
POST /api/v1/admin/invites/:invite_id/accept
Section titled “POST /api/v1/admin/invites/:invite_id/accept”Authentication
Section titled “Authentication”No requireCapability middleware detected on this route. Surface-level auth (admin token, dashboard session, org API key, anonymous proxy) is enforced by the parent router — see Source below.
Middleware
Section titled “Middleware”Other middleware observed on this route (heuristic):
adminBodyLimit
Description
Section titled “Description”POST /api/v1/admin/invites/:invite_id/accept — close the accept loop.
Deletes the pending_invites row + emits the member_accepted audit row.
SECURITY MODEL — the API enforces; the dashboard is UX.
The accept handler is a security device. The admin-token bearer
(whether forwarded by the dashboard or used directly by an operator
tool) MUST NOT be able to bind an arbitrary email to an invite by
passing a fraudulent accepted_by_user_id — that would let anyone
with access to this endpoint silently convert an alice@… invite
into a bob@… acceptance, corrupting the audit chain and (if a
future change makes the accept handler grant state) granting access
to the wrong principal.
Defense steps applied at the API layer (NEVER rely on the dashboard):
1. `accepted_by_user_id` MUST carry the `stytch:` prefix. The admin-token sentinel and other non-Stytch principals are explicitly rejected — the only legitimate accepter is a Stytch-authenticated member.2. Tenant MUST be bound to a Stytch organization (the invite couldn't legitimately be issued otherwise).3. Call Stytch `organizations.members.get` with the bound org + the supplied member id. This proves the member actually exists in the Stytch org the invite was issued for, AND surfaces the member's verified email from Stytch's source of truth.4. Compare the Stytch-returned email (case-insensitive, normalised) to the invite's email. Mismatch → 403 + structured error log. The pending_invites row is NOT deleted on mismatch — a legitimate invitee can still complete the flow.5. Only after all above pass: delete the invite + emit the `member_accepted` audit envelope.The dashboard’s UX-side email check is a fast-feedback layer for users; the API does its own check independently and authoritatively.
Responses
Section titled “Responses”HTTP 400
Section titled “HTTP 400”{ "error": "Invalid JSON body"}HTTP 400
Section titled “HTTP 400”{ "error": "Body must be a JSON object"}HTTP 400
Section titled “HTTP 400”{ "error": "accepted_by_user_id is required"}HTTP 403
Section titled “HTTP 403”{ "error": "accepted_by_user_id must be a Stytch-authenticated principal (stytch:<member_id>)"}HTTP 400
Section titled “HTTP 400”{ "error": "accepted_by_user_id is malformed"}HTTP 404
Section titled “HTTP 404”{ "error": "Invite not found"}HTTP 410
Section titled “HTTP 410”{ "error": "Invite expired"}HTTP 409
Section titled “HTTP 409”{ "error": "Tenant has no bound Stytch organization — invite cannot be completed"}HTTP 503
Section titled “HTTP 503”{ "error": "Stytch client unavailable; cannot verify invitee identity"}HTTP 403
Section titled “HTTP 403”{ "error": "accepted_by_user_id is not a member of this tenant's Stytch organization"}HTTP 403
Section titled “HTTP 403”{ "error": "Accepting principal's email does not match the invite email"}HTTP 404
Section titled “HTTP 404”{ "error": "Tenant not found"}HTTP 409
Section titled “HTTP 409”{ "error": "Seat cap reached for this tier", "consumed": <consumedExcludingThis>, "cap": <cap>}HTTP 200 (default)
Section titled “HTTP 200 (default)”{ "invite_id": <member-expr>, "tenant_id": <member-expr>, "tenant_slug": <logical-expr>, "accepted": true}Source
Section titled “Source”- File:
src/admin/routes.ts - Line: 2360