Skip to content

How to design an API endpoint

This guide defines the API design conventions used across all SympAuthy APIs (Admin, Client, Flow). Contributors adding new endpoints should follow these standards to maintain consistency. The Admin API, Client API, and Flow API are examples of these conventions in action.

TIP

The Flow API is a specialized workflow API that uses step-based URLs and state-driven navigation rather than resource-oriented CRUD. The conventions in this guide apply primarily to resource-oriented APIs (Admin, Client).

URL structure and naming

All API endpoints follow a versioned, resource-oriented URL structure:

/api/v1/{api-name}/{resource}
  • API name: identifies the API (admin, client, flow)
  • Version: embedded in the URL (v1). Increment the version segment when introducing breaking changes.

WARNING

OAuth 2.1 and OpenID Connect endpoints follow their respective RFC paths (/api/oauth2/, /.well-known/) and do not use the /api/v1/ prefix.

Resource naming rules

  • Use plural nouns for collections: users, clients, sessions
  • Use kebab-case for multi-word URL segments: reset-password, sign-in
  • Use path parameters for identifiers: {user_id}, {client_id}, {session_id}

URL patterns

PatternUsageExample
/api/v1/{api}/{resource}CollectionGET /api/v1/admin/users
/api/v1/{api}/{resource}/{id}Individual resourceGET /api/v1/admin/users/{user_id}
/api/v1/{api}/{resource}/{id}/{action}Action on a resourcePOST /api/v1/admin/users/{user_id}/disable
/api/v1/{api}/{resource}/{id}/{sub-resource}Nested resourceGET /api/v1/admin/users/{user_id}/consents

HTTP methods

MethodUsageRequest BodyIdempotentExample
GETRetrieve a resource or collectionNoneYesGET /api/v1/admin/users
POSTCreate a resource or trigger an actionYesNoPOST /api/v1/admin/users
PATCHPartial update of a resourceYes (partial)NoPATCH /api/v1/admin/users/{user_id}
DELETERemove a resourceNoneYesDELETE /api/v1/admin/users/{user_id}

PUT is not used. PATCH is preferred because SympAuthy APIs use partial updates — only provided fields are modified, omitted fields remain unchanged.

POST is used for actions that do not map cleanly to CRUD operations (e.g., disable, enable, reset-password, logout). These actions use a sub-path on the resource: POST /api/v1/admin/users/{user_id}/disable.

HTTP status codes

StatusWhen to useExample
200 OKSuccessful GET, PATCH, action POST, DELETEReturn the resource or confirmation
201 CreatedSuccessful resource creation (POST)Return the created resource
400 Bad RequestInvalid input, business rule violationDefault for all BusinessException
401 UnauthorizedMissing or invalid authentication token{"error": "unauthorized", ...}
403 ForbiddenValid token but insufficient scope{"error": "forbidden", ...}
404 Not FoundResource does not exist{"error": "not_found", ...}
409 ConflictResource already exists or state conflict{"error": "conflict", ...}
500 Internal Server ErrorUnexpected server error{"error": "internal_server_error", ...}

DELETE operations return 200 with a confirmation body so the caller can verify which resource was affected:

json
{
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "deleted": true
}

Request and response format

Content type

All API requests and responses use application/json.

WARNING

The OAuth 2.1 token endpoint uses application/x-www-form-urlencoded as required by the RFC specification.

Property naming and data types

  • Property names: snake_case (user_id, created_at, granted_scopes)
  • Timestamps: ISO 8601 in UTC with Z suffix ("2026-03-06T10:00:00Z")
  • Identifiers: lowercase UUID with hyphens ("550e8400-e29b-41d4-a716-446655440000")
  • Booleans: JSON native true/false (never strings)
  • Null handling: include the property with null value rather than omitting it when the property is part of the schema

Collection responses

Collection responses wrap the array in a key matching the resource name (plural). Pagination metadata sits at the same level as the array:

json
{
  "users": [
    {
      "user_id": "550e8400-e29b-41d4-a716-446655440000",
      "status": "enabled",
      "created_at": "2026-01-15T14:30:00Z"
    }
  ],
  "page": 0,
  "size": 20,
  "total": 42
}

Single resource responses

Single resource responses return the object directly, not wrapped in a key:

json
{
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "enabled",
  "created_at": "2026-01-15T14:30:00Z"
}

Pagination

All list endpoints use page-based pagination.

Query parameters

ParameterTypeDefaultDescription
pageinteger0Zero-indexed page number
sizeinteger20Number of results per page

Response metadata

The response includes pagination metadata alongside the data array:

PropertyDescription
pageCurrent page number
sizeCurrent page size
totalTotal number of records matching the query

Example

http
GET /api/v1/admin/users?page=1&size=10
json
{
  "users": [ ... ],
  "page": 1,
  "size": 10,
  "total": 42
}

Filtering and sorting

Filtering

Filtering uses query parameters named after the property being filtered:

http
GET /api/v1/admin/users?status=enabled
GET /api/v1/admin/sessions?user_id=550e8400-e29b-41d4-a716-446655440000

Filtering and pagination parameters can be combined:

http
GET /api/v1/admin/users?status=enabled&page=0&size=10

Only add filtering to an endpoint when there is a concrete use case for it.

Sorting

When sorting is needed, use the following query parameters:

ParameterTypeDefaultDescription
sortstringProperty to sort by
orderstringascSort direction (asc or desc)
http
GET /api/v1/admin/users?sort=created_at&order=desc

Error responses

All errors follow a consistent format:

json
{
  "error": "<error_code>",
  "error_description": "<human-readable description>"
}
  • error: machine-readable code (lowercase, underscores)
  • error_description: human-readable message for debugging

Authentication and authorization errors use standardized codes:

json
{
  "error": "unauthorized",
  "error_description": "Missing or invalid access token."
}
json
{
  "error": "forbidden",
  "error_description": "The access token does not include the required scope: admin:users:read"
}

Business errors follow the structured error code pattern defined in How to throw an exception. See that guide for details on detailsId, descriptionId, and the distinction between recoverable and unrecoverable exceptions.

WARNING

Never expose internal stack traces or implementation details in error responses. The error_description should help troubleshoot without revealing system internals.

Authentication

All endpoints require authentication by default using a Bearer token in the Authorization header:

http
GET /api/v1/admin/users
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...

Anonymous access is the exception, not the rule. Only endpoints where the caller cannot yet have a token (e.g., flow configuration, OAuth authorize) should allow unauthenticated access.

The Flow API uses a dedicated state-based authentication mechanism:

Request typeState location
GET /api/v1/flow/**?state= query parameter
POST /api/v1/flow/**Authorization: State <jwt> header

See Security for the full description of authentication mechanisms and the Flow API for details on state management.

Endpoint documentation template

When adding a new endpoint, document it in the relevant API doc file (Admin API, Client API, or Flow API) using this template:

markdown
#### <Endpoint Name>

**Path**: `/api/v1/{api}/{resource}`

**Method**: GET | POST | PATCH | DELETE

**Authentication**: <Bearer token with `scope:name` scope | State token | None>

**Purpose**: <One sentence describing what the endpoint does>

**Path Parameters** (if applicable):

- `param_name`: Description

**Query Parameters** (if applicable):

- `param_name` (optional|required): Description (default: `value`)

**Request Format** (if applicable):

\`\`\`json
{ ... }
\`\`\`

**Response Format**:

\`\`\`json
{ ... }
\`\`\`

**Properties**:

- `property_name`: Description
    - `nested_property`: Description

**Use Cases**:

- Use case 1
- Use case 2
- Use case 3

Separate endpoints with a horizontal rule (---). Group related endpoints under a shared heading (e.g., "User Management", "Access Control").