Accounts

POST /v1/admin/accounts creates a new organization, provisions a subdomain, and returns a tenant key.

One-Time Key

The tenantKey in the response is your only chance to save it. It is hashed before storage and cannot be retrieved later. If you lose it, use the key rotation endpoint to generate a new one (which requires the current key).

Create Account

Request

POST /v1/admin/accounts Content-Type: application/json

No authentication required.

Schema

Field Type Required Constraints Description
organizationName string Yes 2 - 120 chars Display name for the organization
ownerEmail string Yes Valid email Contact email for the account owner
slug string Yes 2 - 40 chars, ^[a-z0-9-]+$ URL-safe identifier; becomes <slug>.formdata.dev

Example

curl -X POST https://api.formdata.dev/v1/admin/accounts \
  -H "Content-Type: application/json" \
  -d '{
    "organizationName": "Acme Corp",
    "ownerEmail": "[email protected]",
    "slug": "acme"
  }'

Response (200)

{
  "ok": true,
  "account": {
    "organizationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "organizationName": "Acme Corp",
    "slug": "acme",
    "ownerEmail": "[email protected]",
    "subdomain": "acme.formdata.dev"
  },
  "tenantKey": "sk_live_abc123..."
}
Field Description
account.organizationId UUID of the new organization
account.subdomain Custom hostname provisioned on Cloudflare
tenantKey Secret key (sk_ prefix) for all admin API calls

Errors

Status Error Cause
400 Validation error Missing or invalid fields
409 Slug is already taken Another organization has this slug

Subdomain Provisioning

On account creation, formdata.dev automatically provisions a custom hostname via Cloudflare for SaaS:

  1. A POST is made to the Cloudflare Custom Hostnames API for <slug>.formdata.dev
  2. SSL is provisioned using HTTP DV validation
  3. The cf_custom_hostname_id is stored for the organization
Non-Blocking

Subdomain provisioning runs best-effort. If the Cloudflare API call fails, the account is still created successfully. The api.formdata.dev base URL always works regardless of subdomain status.