API Guidelines

API Guidelines

General

  • RESTful JSON API
  • Versioned via URL prefix: /api/v1/
  • Authentication: Bearer token (Zitadel OIDC)
  • Tenant scoping: every request is scoped to a tenant (from token)

URL Conventions

  • Plural nouns: /api/v1/groups, /api/v1/members
  • Nested resources: /api/v1/groups/{groupId}/members
  • Actions (non-CRUD): POST /api/v1/groups/{groupId}/join
  • kebab-case for multi-word paths: /api/v1/event-registrations

HTTP Methods

  • GET — read (list or detail), never changes state
  • POST — create resource or trigger action
  • PUT — full replace (rare, prefer PATCH)
  • PATCH — partial update
  • DELETE — remove resource

Response Format

{
  "data": { ... },
  "meta": {
    "page": 1,
    "perPage": 25,
    "total": 142
  }
}

Error Format

{
  "error": {
    "code": "MEMBER_NOT_FOUND",
    "message": "Member with ID xyz was not found.",
    "details": []
  }
}

Status Codes

  • 200 — success (GET, PATCH, PUT)
  • 201 — created (POST)
  • 204 — no content (DELETE)
  • 400 — validation error
  • 401 — unauthenticated
  • 403 — unauthorized (missing permission)
  • 404 — resource not found
  • 409 — conflict (duplicate, state violation)
  • 422 — unprocessable entity (business rule violation)
  • 500 — server error (should never happen intentionally)

Pagination

  • Cursor-based for large lists (preferred)
  • Offset-based where cursor doesn’t make sense
  • Default page size: 25, max: 100
  • Query params: ?page=1&perPage=25 or ?cursor=abc123

Filtering & Sorting

  • Filter via query params: ?status=active&groupId=123
  • Sort: ?sort=createdAt&order=desc
  • Search: ?q=searchterm

Timezone Handling

  • All timestamps in responses are ISO 8601 with UTC offset: 2026-03-15T14:30:00Z
  • Entities with a timezone (events, organizations) include a timezone field: "timezone": "Europe/Zurich"
  • Clients are responsible for converting UTC to the user’s local time or the entity’s timezone
  • Accept timestamps in requests as UTC or with explicit offset — reject ambiguous local times
  • The Accept-Timezone header is NOT used — timezone context comes from the entity, not the request

Tenant Scoping

  • Every query MUST be scoped to the current tenant
  • Tenant ID comes from the authentication token
  • Never accept tenant ID as a request parameter
  • Cross-tenant access is a critical security violation