Turn on the services you need.
Each service (Auth, Mail, Waitlist, Social) is opt-in per workspace. Until you activate, every endpoint on that service returns 403 SERVICE_NOT_ENABLED before any data leaks.
1
Why activation is per-workspace
Two reasons:
- Billing isolation — services charge per workspace based on usage. Activating Mail for one of your workspaces (production) without activating it for another (dev) keeps the bill clean.
- Defence in depth — a misconfigured key shouldn't accidentally hit a service the workspace never wanted. The
SERVICE_NOT_ENABLEDguard runs before any service-specific logic, so a caller can't enumerate a non-activated surface.
2
Enable a service
From the console (Settings → Services → flip the toggle) or API:
# Enable Mail
curl -X POST https://api.platform-auth.productcraft.co/v1/workspaces/acme/services/mail \
-H 'authorization: Bearer <cookie-or-pak>' \
-H 'content-type: application/json' \
-d '{
"settings": {
"from_address": "noreply@acme.com",
"notifications_via_envoi": true
}
}'workspace.service.enable permission required. Enabling a service for the first time fires the service's initial provisioning — e.g. Auth doesn't need much, Mail may seed default templates if the workspace has none.
3
Service-specific settings
Each service exposes its own settings shape:
# Mail
{
"from_address": "noreply@acme.com",
"notifications_via_envoi": true, # used by Auth verify/reset
"default_brand": { "logo_url": "...", "accent_color": "#..." }
}
# Auth
{
"default_signup_role": "member",
"session_ttl_seconds": 3600,
"refresh_ttl_seconds": 2592000 # 30 days
}
# Waitlist
{
"default_capacity": 500,
"default_captcha_provider": "recaptcha" # or "hcaptcha" | "turnstile" | null
}
# Social
{
"default_visibility": "public",
"moderation_required": false
}Update via PATCH /v1/workspaces/:slug/services/:service/settings. Settings update doesn't fire provisioning a second time; it's just metadata.
4
Disable
curl -X DELETE https://api.platform-auth.productcraft.co/v1/workspaces/acme/services/mail \
-H 'authorization: Bearer <cookie-or-pak>'Disable doesn't delete data. Domains, templates, messages, suppression list — all preserved. Subsequent calls to the service's endpoints just return 403 SERVICE_NOT_ENABLED. Re-enabling immediately restores access.
For permanent removal, delete the workspace (Chapter 1) or open a customer-support ticket for service-scoped purges.
5
List active services
curl https://api.platform-auth.productcraft.co/v1/workspaces/acme/services \
-H 'authorization: Bearer <cookie-or-pak>'{
"services": [
{ "service": "auth", "enabled": true, "enabled_at": "..." },
{ "service": "mail", "enabled": true, "enabled_at": "..." },
{ "service": "waitlist", "enabled": false },
{ "service": "social", "enabled": false }
]
}6
The SERVICE_NOT_ENABLED guard in practice
Useful for SDKs and customer support — what error to expect when a service hasn't been turned on.
Every service-specific endpoint runs this check first:
HTTP/1.1 403 Forbidden
{
"statusCode": 403,
"code": "SERVICE_NOT_ENABLED",
"service": "mail",
"message": "Service 'mail' is not enabled on this workspace.",
"remediation": "Owner: enable via console → Settings → Services."
}This 403 isn't a permission failure — even a PAK with full mail.* permission gets 403 if the service isn't enabled. Activation is a structural prerequisite, not an authz outcome.