Skip to content

API, mobile, tenancy and privacy

Rule. Every endpoint is documented in OpenAPI. The same authority applies on web and native tokens. Everything the web does is available to the apps, within the person’s role.

Why. The apps are built from the API contract. An undocumented endpoint cannot be used safely, and a second permission system for phones would drift.

How to apply.

  • Document every route: request and response types, auth requirement, typed errors. Keep the snapshot in the repository and regenerate it and the generated client types whenever routes or models change; CI fails on any difference. Next.js apps publish /api/v1/openapi.json.
  • API reference for people, from the same spec. Every product with an API publishes:
    • the OpenAPI 3.1 document (JSON, and YAML where useful) at a stable URL (/api/schema/ or /api/v1/openapi.json) and as a committed snapshot;
    • Swagger UI (try-it-out, at /api/docs/ or /docs/) and ReDoc (readable reference, at /redoc/), both served from that document, both behind the same auth rules as the API (public in self-host by default, can be turned off in production by a setting);
    • the reference on the product’s docs site too (rendered with Redocly/ReDoc), next to guides for auth, errors, pagination, rate limits, webhooks and versioning.
  • Spec quality. Every operation has an operationId (stable, no _2 suffixes), a summary and description, tags that match the areas, request/response schemas with examples, every error response (400/401/403/404/409/422/429) with the shared error schema, the security scheme (session, bearer, device token, API client HMAC), and pagination parameters. Enums are named. Breaking changes need a new version prefix (/api/v2/) and a note in the changelog.
  • Lint the spec with Redocly CLI (redocly lint with the org ruleset: operationId required and unique, tags declared, no unused components, 4xx responses documented, examples valid) and bundle it (redocly bundle) for the docs site. In CI this runs in the contract stage (section 14) and fails on errors.
  • Generated clients (TypeScript, Kotlin, Swift) come from the same snapshot; nobody hand-writes request types.
  • Errors have a stable, untranslated code and a translated message, in one consistent body.
  • Throttle abuse-prone routes. Answer 429 with Retry-After; clients back off.
  • Native tokens: device grants (App Attest on iOS, Play Integrity on Android) refuse platform-admin accounts, can be revoked per device and carry the same per-space authority as the browser session.
  • Never cache a response that carries a one-time secret. Never put bearer values in URLs.
  • Machine API clients are tenant-bound bearer identities with explicit scopes, minimal DTOs, rotating credentials, and a protected-route registry; an unknown route is denied.
  • Outbound webhooks are signed (HMAC), sent only after commit, point only at public addresses (checked at save and at delivery), and carry minimal DTOs without personal data.
  • Native apps: phone and tablet layouts, light and dark, largest text size, real offline behaviour where a flow needs it, push through FCM (Android) and APNs (iOS), and a server-URL entry that validates the health endpoint. Missing server support is shown as honestly unavailable, never faked.
  • Android and iOS keep parity: same screens, names, flows and error copy.

Across the org. SpaceWorks: OpenAPI snapshot, generated client, route-coverage check, throttle tests, device grants. Machines, Inventory, Bookings: /api/v1/openapi.json, API keys, signed webhooks. Events: machine API v1 and a mobile API spec, webhooks. Workspace: bearer API tokens, integration webhooks, MCP. Organization: OpenAPI endpoint not found in the tree; not checked. Android and iOS: generated client from api/openapi.json. Swagger UI and ReDoc: SpaceWorks serves both (/docs/, /redoc/, schema at /schema/) from drf-spectacular; Redocly lint is not yet in any pipeline; the Next.js apps’ Swagger/ReDoc pages are not checked.


Rule. Every record belongs to one tenant, every query is scoped to it, and public surfaces never expose private fields.

Why. Forgetting a scope is a cross-tenant leak, not a bug.

How to apply.

  • The tenant (makerspace, space, organisation, workspace) id sits on every domain row, with composite foreign keys to its parent. Resolve the tenant on the server, never from a client-sent id. Where the database supports it, add row-level security as a second layer.
  • Public pages never expose storage locations, box ids, QR codes, scan history, evidence photos, requester history or hidden counts. Per-item switches decide what is public and what count mode (exact, status only, hidden) is shown.
  • Hidden or unpublished things return the same 404 as things that do not exist (short links, slugs).
  • Opaque, unguessable ticket, invitation, reset and certificate links; bearer capabilities are stored only as hashes and treated as secrets.
  • Personal data is encrypted per scope; contact details never appear in public output; a new model that holds personal data must be classified or the build fails.
  • Public names, profiles and presence follow the person’s own settings; everyone else is counted, not named.
  • Private storage: uploads are validated, decoded, resized, metadata-stripped and served through an application route with short-lived signed links.
  • Personal data export and account deletion are available to every person.
  • Deleting a space deletes its data and files completely, after audited steps. Archive is reversible; purge is not.
  • Security headers, CSP nonces, trusted-proxy handling, Redis-backed fail-closed rate limits, MFA, breached password checks and new-device alerts are not loosened without a documented change. Never read, print or commit .env values; if a credential is exposed, rotate it.

Across the org. All server products: application-enforced tenant isolation. SpaceWorks adds Postgres row-level security. Machines, Bookings, Organization state “no RLS; isolation in the application”. Android and iOS: credentials in Keychain / Android keystore only, no secrets in the repository.

Part of Space Works product rules, published from docs/product-rules.md. Edit it there.