Authentication
All endpoints are prefixed with /v1. Honeycomb supports two authentication methods: Bearer tokens (JWT via Supabase Auth) and API keys.
Authentication Methods
Section titled “Authentication Methods”Bearer Token (JWT)
Section titled “Bearer Token (JWT)”Used for user sessions. Obtained via sign-in or token refresh. Include the token in the Authorization header:
Authorization: Bearer <access_token>Tokens are standard Supabase Auth JWTs. They expire based on the expires_in value returned at sign-in. Use the refresh endpoint to obtain a new token pair before expiry.
API Key
Section titled “API Key”Used for scripts, integrations, and server-to-server communication. Include the key in the X-API-Key header:
X-API-Key: <api_key>API keys are SHA-256 hashed before storage in the api_keys table. The plaintext key is only shown once at creation time.
Middleware
Section titled “Middleware”The API uses a layered middleware system for authentication and authorization:
| Middleware | Behavior |
|---|---|
authenticate() | Runs on every request. Non-blocking. Populates req.user if a valid token or API key is present. |
requireAuth | Blocks the request with 401 Unauthorized if req.user is not set. |
requireAdmin | Blocks the request with 403 Forbidden if req.user.role is not admin. |
apiKeyAuth | Strict gate that accepts either X-API-Key or Authorization: Bearer. Returns 401 if neither is valid. |
req.user Shape
Section titled “req.user Shape”When authentication succeeds, req.user is populated with:
interface ReqUser { id: string; // UUID email: string; role: string; // e.g. "user", "admin" type?: string; // account type status?: string; // account status username?: string;}Endpoints
Section titled “Endpoints”Sign Up
Section titled “Sign Up”POST /v1/auth/sign-upRegister a new user account. No authentication required.
Request Body:
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
email | string | Yes | Valid email format | User’s email address |
password | string | Yes | Min 8 characters | Account password |
first_name | string | No | — | User’s first name |
last_name | string | No | — | User’s last name |
Response 201 Created:
{ "user": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "email": "jane@example.com" }}Error Responses:
| Status | Body | Condition |
|---|---|---|
400 | { "error": "Validation error", "details": [...] } | Invalid email or short password |
409 | { "error": "Email already registered" } | Duplicate email address |
Sign In
Section titled “Sign In”POST /v1/auth/sign-inAuthenticate with email and password. Returns access and refresh tokens along with user info.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | User’s email |
password | string | Yes | Account password |
Response 200 OK:
{ "session": { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "v1.MjA0ZDFhYmUtYTdk...", "expires_in": 3600, "expires_at": 1711843200 }, "user": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "email": "jane@example.com", "role": "user" }}| Response Field | Type | Description |
|---|---|---|
session.access_token | string | JWT for authenticating subsequent requests |
session.refresh_token | string | Token used to obtain a new access token |
session.expires_in | number | Token lifetime in seconds |
session.expires_at | number | Unix timestamp when the access token expires |
user.id | string | User UUID |
user.email | string | User’s email |
user.role | string | User’s role (e.g. "user", "admin") |
Error Responses:
| Status | Body | Condition |
|---|---|---|
401 | { "error": "Invalid credentials" } | Wrong email or password |
Sign Out
Section titled “Sign Out”POST /v1/auth/sign-outInvalidates the current session. Requires authentication.
Headers:
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <access_token> | Yes |
Request Body: None
Response 200 OK:
{ "message": "Signed out"}Error Responses:
| Status | Body | Condition |
|---|---|---|
401 | { "error": "Not authenticated" } | Missing or invalid token |
Forgot Password
Section titled “Forgot Password”POST /v1/auth/forgot-passwordSends a password reset link to the given email address. No authentication required.
Request Body:
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
email | string | Yes | Valid email format | Account email address |
Response 200 OK:
{ "message": "If the email exists, a reset link has been sent"}Reset Password
Section titled “Reset Password”POST /v1/auth/reset-passwordResets the user’s password using the recovery token from the password reset email. Requires authentication — the recovery token from the email must be passed as a Bearer token.
Headers:
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <recovery_token> | Yes |
Request Body:
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
password | string | Yes | Min 8 characters | New password |
Response 200 OK:
{ "message": "Password reset successful"}Error Responses:
| Status | Body | Condition |
|---|---|---|
400 | { "error": "Missing password" } | password field not provided |
401 | { "error": "Authentication required — pass the recovery token as Bearer" } | Missing or invalid recovery token |
Verify Email
Section titled “Verify Email”POST /v1/auth/verify-emailVerifies a user’s email address using the token hash from the verification email.
Request Body:
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
token_hash | string | Yes | — | Token hash from the verification email |
type | string | Yes | Enum: "email" | Verification type |
Response 200 OK:
{ "session": { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "v1.MjA0ZDFhYmUtYTdk...", "expires_in": 3600 }, "user": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "email": "jane@example.com" }}Error Responses:
| Status | Body | Condition |
|---|---|---|
400 | { "error": "token_hash and type are required" } | Missing required fields |
400 | { "error": "Invalid token" } | Expired or malformed token hash |
Resend Verification
Section titled “Resend Verification”POST /v1/auth/resend-verificationResends the email verification link. No authentication required.
Request Body:
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
email | string | Yes | Valid email format | Email address to verify |
Response 200 OK:
{ "message": "Verification email resent"}Error Responses:
| Status | Body | Condition |
|---|---|---|
400 | { "error": "Email is required" } | email not provided |
Refresh Token
Section titled “Refresh Token”POST /v1/auth/refreshExchange a valid refresh token for a new access/refresh token pair. No authentication header required — the refresh token is passed in the body.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
refresh_token | string | Yes | Refresh token from a previous sign-in |
Response 200 OK:
{ "session": { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "v1.NTY3ZjlkMmEtMGRi...", "expires_in": 3600, "expires_at": 1711846800 }}Error Responses:
| Status | Body | Condition |
|---|---|---|
401 | { "error": "Invalid or expired refresh token" } | Token is expired, revoked, or invalid |
Get Session
Section titled “Get Session”GET /v1/auth/sessionReturns the currently authenticated user’s session information. Requires authentication.
Headers:
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <access_token> | Yes |
Query Parameters: None
Request Body: None
Response 200 OK:
{ "user": { "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "email": "jane@example.com", "role": "user", "type": "creator", "status": "active", "username": "janedoe" }}| Response Field | Type | Nullable | Description |
|---|---|---|---|
user.id | string | No | User UUID |
user.email | string | No | User’s email address |
user.role | string | No | User’s role (e.g. "user", "admin") |
user.type | string | Yes | Account type |
user.status | string | Yes | Account status (e.g. "active") |
user.username | string | Yes | Username, if set |
Error Responses:
| Status | Body | Condition |
|---|---|---|
401 | { "error": "Not authenticated" } | Missing or invalid token |
Usage Examples
Section titled “Usage Examples”Full Authentication Flow
Section titled “Full Authentication Flow”# 1. Sign upcurl -X POST https://api.honeycomb.example/v1/auth/sign-up \ -H "Content-Type: application/json" \ -d '{ "email": "jane@example.com", "password": "secureP@ss1", "first_name": "Jane", "last_name": "Doe" }'
# 2. Sign in (after email verification)curl -X POST https://api.honeycomb.example/v1/auth/sign-in \ -H "Content-Type: application/json" \ -d '{ "email": "jane@example.com", "password": "secureP@ss1" }'
# 3. Use access token for authenticated requestscurl https://api.honeycomb.example/v1/auth/session \ -H "Authorization: Bearer eyJhbGciOi..."
# 4. Refresh when token is about to expirecurl -X POST https://api.honeycomb.example/v1/auth/refresh \ -H "Content-Type: application/json" \ -d '{ "refresh_token": "v1.MjA0ZDFh..." }'
# 5. Sign outcurl -X POST https://api.honeycomb.example/v1/auth/sign-out \ -H "Authorization: Bearer eyJhbGciOi..."// Sign inconst res = await fetch('https://api.honeycomb.example/v1/auth/sign-in', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: 'jane@example.com', password: 'secureP@ss1', }),});const { session, user } = await res.json();
// Authenticated requestconst session_res = await fetch('https://api.honeycomb.example/v1/auth/session', { headers: { Authorization: `Bearer ${session.access_token}` },});# Use an API key instead of a Bearer tokencurl https://api.honeycomb.example/v1/auth/session \ -H "X-API-Key: hc_live_a1b2c3d4e5f6..."