Skip to main content
Switchbord exposes a small set of authenticated endpoints under /api/templates in the control-plane app (apps/app) for managing the workspace’s WhatsApp template library. All routes require a signed-in operator with settings access (owner, admin, or developer); the seed route additionally requires owner or admin. Requests are scoped to the caller’s active workspace — you cannot see or mutate templates from another workspace. Auth errors return 401 unauthenticated or 403 forbidden.

Summary


GET /api/templates

List every template for the active workspace, sorted by most recently updated. Response 200
Errors401 unauthenticated, 403 forbidden, 500 internal_error.

POST /api/templates

Create a new template draft. The template is stored with status = "draft" and is not submitted to Meta. Use POST /api/templates/{id}/submit after the draft is ready. Request body
Fields:
  • name (string, required) — lowercase and non-matching characters are stripped to _. See name-regex.
  • language (string, default "en_US") — Meta locale code, e.g. "it", "en_US", "pt_BR".
  • category ("MARKETING" | "UTILITY" | "AUTHENTICATION", default "UTILITY").
  • parameterFormat ("POSITIONAL" | "NAMED", default "POSITIONAL").
  • components (array) — the component shape from Meta’s schema.
Response 200
Errors400 create_failed with message describing the DB or validation error, plus auth errors.

GET /api/templates/

Fetch a single template by id. Returns 404 not_found if the id does not exist in the caller’s workspace. Response 200 — same shape as a list entry.

PUT /api/templates/

Update one or more fields on a draft template. Sent fields are patched; unsent fields are left untouched. Request body (all fields optional)
Response 200
Errors400 update_failed, auth errors.

DELETE /api/templates/

Delete a draft template. Only templates in status = "draft" can be deleted — once submitted, templates are retained for audit. A non-draft template returns 409 conflict. Response 200
Errors404 not_found, 409 conflict with "Only draft templates can be deleted", auth errors.

POST /api/templates//submit

Submit a draft template to Meta’s POST /{waba_id}/message_templates endpoint. Switchbord first runs the full client-side validator (the same rules documented in Template rules); if any check fails, no call is made to Meta and a 400 validation_failed is returned with the failing issues array. On success, the template row is updated with meta_template_id, status = "pending_approval", and rejection_reason = null. On Meta rejection (HTTP 4xx with an error body), the row is updated with status = "rejected" and rejection_reason = Meta’s human-readable message, and a 400 meta_submit_failed is returned to the caller with the full Meta error payload. Response 200
Errors
  • 400 validation_failed — client-side validator rejected the draft. issues is the Zod issue array.
  • 400 meta_submit_failed — Meta returned an error. meta is the raw Meta error object (code, subcode, error_user_msg, error_data.details).
  • 400 no_waba — no WhatsApp Business Account configured for the workspace.
  • 400 no_access_token — no Meta access token in the workspace vault.
  • 404 not_found, auth errors.

POST /api/templates/seed-starter-pack

Admin-only one-shot seeder that submits the GB Viaggi starter pack (8 UTILITY + 5 MARKETING, Italian + English where applicable) to Meta and upserts the matching rows in the local templates table. Safe to re-run — the upsert key is (workspace_id, name, locale). Required role: owner or admin. Request body — empty. Response 200
Per-entry status:
  • submitted — accepted by Meta, row stored as pending_approval.
  • error — Meta or DB error, row stored as rejected with the Meta message.
  • skipped — reserved; not currently emitted.
Errors
  • 400 no_waba — no WhatsApp Business Account configured.
  • 400 no_access_token — vault secret meta-access-token missing.
  • 500 internal_error — unhandled failure.
  • 401 unauthenticated, 403 forbidden — caller lacks owner/admin role.

Two internal, non-public routes exist in apps/api for machine-to-machine sync flows:
  • POST /internal/templates — worker-to-API template upsert after a webhook event.
  • POST /internal/templates/sync — full-library resync from Meta back to Switchbord.
These are not exposed publicly and should not be called from the frontend. See Internal API spec for the authoritative list.

OpenAPI

The machine-readable shape for these endpoints will be generated into apps/docs/api-reference/openapi.json by pnpm openapi:generate. Run the generator after changing a route to keep the spec in sync.