OAuth 2.0
Third-party applications use OAuth 2.0 authorization code flow to act on behalf of hzel users. All endpoints are served from https://api.hzel.org.
POST /api/v1/oauth/apps
Section titled “POST /api/v1/oauth/apps”Register a new OAuth application. The client_secret is returned once — store it immediately.
| Header | Value | Required |
|---|---|---|
Authorization | Bearer <token> | Yes |
Content-Type | application/json | Yes |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Application display name |
description | string | No | Shown on the consent screen |
redirect_uris | string[] | Yes | Must be https:// in production |
Success 200
{ "data": { "id": "01942cf7-…", "client_id": "01942cf8-…", "client_secret": "hzcs_…", "name": "My App", "redirect_uris": ["https://myapp.example.com/callback"], "created_at": "2026-03-28T00:00:00Z" }}Client secrets are prefixed hzcs_ and stored as SHA-256. Use POST /api/v1/oauth/apps/{id}/secret to rotate.
Errors
| Status | error.code | Reason |
|---|---|---|
400 | BAD_REQUEST | Missing or invalid fields |
401 | UNAUTHORIZED | Missing or invalid auth |
Authorization code flow
Section titled “Authorization code flow”Step 1 — Redirect the user
Section titled “Step 1 — Redirect the user”GET /api/v1/oauth/authorize
Section titled “GET /api/v1/oauth/authorize”Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
client_id | string | Yes | Your app’s UUID |
redirect_uri | string | Yes | Must match a registered redirect URI |
response_type | string | Yes | Must be code |
state | string | Yes | Random value; verify on callback to prevent CSRF |
Response: 302 Found → hzel consent page.
Errors
| Status | error.code | Reason |
|---|---|---|
400 | BAD_REQUEST | Missing or invalid parameters |
404 | NOT_FOUND | client_id not found |
Step 2 — User approves
Section titled “Step 2 — User approves”The user sees your app name and grants or denies access via the hzel consent UI.
On approval, the browser is redirected to your redirect_uri:
https://myapp.example.com/callback?code=<auth_code>&state=<your_state>On denial:
https://myapp.example.com/callback?error=access_denied&state=<your_state>Always verify the state value matches what you sent in step 1.
Step 3 — Exchange the code
Section titled “Step 3 — Exchange the code”POST /api/v1/oauth/token
Section titled “POST /api/v1/oauth/token”| Header | Value | Required |
|---|---|---|
Content-Type | application/json | Yes |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
grant_type | string | Yes | "authorization_code" |
code | string | Yes | The auth code from the callback |
redirect_uri | string | Yes | Must exactly match step 1 |
client_id | string | Yes | Your app’s UUID |
client_secret | string | Yes | Your hzcs_… secret |
Success 200
{ "access_token": "eyJ…", "token_type": "Bearer", "expires_in": 900, "refresh_token": "hzrt_…"}The token endpoint does not wrap the response in a data envelope — it follows the OAuth 2.0 RFC directly.
Errors
| Status | error.code | Reason |
|---|---|---|
400 | BAD_REQUEST | Invalid grant_type, bad code, or redirect_uri mismatch |
401 | UNAUTHORIZED | Invalid client_id or client_secret |
Refresh tokens
Section titled “Refresh tokens”Use the same POST /api/v1/oauth/token endpoint with grant_type: "refresh_token":
| Field | Type | Required | Description |
|---|---|---|---|
grant_type | string | Yes | "refresh_token" |
refresh_token | string | Yes | The hzrt_… token from a previous response |
client_id | string | Yes | Your app’s UUID |
client_secret | string | Yes | Your hzcs_… secret |
Refresh tokens rotate on every use — the previous token is revoked immediately.
POST /api/v1/oauth/token/revoke
Section titled “POST /api/v1/oauth/token/revoke”Revoke a refresh token. Idempotent per RFC 7009.
| Header | Value | Required |
|---|---|---|
Content-Type | application/json | Yes |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
refresh_token | string | Yes | The hzrt_… token to revoke |
client_id | string | Yes | Your app’s UUID |
client_secret | string | Yes | Your hzcs_… secret |
Success 200 — { "data": "revoked" }
Managing your application
Section titled “Managing your application”All management endpoints require Authorization: Bearer <token>.
| Method | Endpoint | CSRF | Description |
|---|---|---|---|
GET | /api/v1/oauth/apps | No | List your apps |
GET | /api/v1/oauth/apps/{id} | No | Get one app |
PATCH | /api/v1/oauth/apps/{id} | Session only | Update name, description, redirect URIs |
DELETE | /api/v1/oauth/apps/{id} | Session only | Revoke the app and all its tokens |
POST | /api/v1/oauth/apps/{id}/secret | Session only | Rotate the client secret |