/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 200401 unauthenticated, 403 forbidden, 500 internal_error.
POST /api/templates
Create a new template draft. The template is stored withstatus = "draft" and is not submitted to Meta. Use POST /api/templates/{id}/submit after the draft is ready.
Request body
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.
400 create_failed with message describing the DB or validation error, plus auth errors.
GET /api/templates/
Fetch a single template by id. Returns404 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)400 update_failed, auth errors.
DELETE /api/templates/
Delete a draft template. Only templates instatus = "draft" can be deleted — once submitted, templates are retained for audit. A non-draft template returns 409 conflict.
Response 200
404 not_found, 409 conflict with "Only draft templates can be deleted", auth errors.
POST /api/templates//submit
Submit a draft template to Meta’sPOST /{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
400 validation_failed— client-side validator rejected the draft.issuesis the Zod issue array.400 meta_submit_failed— Meta returned an error.metais the raw Metaerrorobject (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 (8UTILITY + 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
status:
submitted— accepted by Meta, row stored aspending_approval.error— Meta or DB error, row stored asrejectedwith the Meta message.skipped— reserved; not currently emitted.
400 no_waba— no WhatsApp Business Account configured.400 no_access_token— vault secretmeta-access-tokenmissing.500 internal_error— unhandled failure.401 unauthenticated,403 forbidden— caller lacks owner/admin role.
Related internal endpoints
Two internal, non-public routes exist inapps/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.
OpenAPI
The machine-readable shape for these endpoints will be generated intoapps/docs/api-reference/openapi.json by pnpm openapi:generate. Run the generator after changing a route to keep the spec in sync.