api reference
Organizations API
Create organizations and read user-scoped or organization-scoped inbox metrics.
These routes cover both organization creation and dashboard reporting. Creation is authenticated but not organization-scoped yet. The stats route is user-scoped, while the activity and summary routes require organization scope.
Create organization
/api/organizationsCreate a new organization for the authenticated user and provision its starter inbox when possible.
- Auth
- Authenticated endpoint. Organization scope is not required because this route creates the organization.
- Success
201
Request headers
| Header | Example | Requirement | Details |
|---|---|---|---|
Cookie session or X-API-Key | Cookie: <better-auth session> or X-API-Key: spin_... | Required | All documented product endpoints require an authenticated user session or a valid Better Auth API key. |
Content-Type | application/json | Required | JSON request body is required. |
Request body
| Field | Type | Requirement | Details |
|---|---|---|---|
name | string | Required | Organization display name. Constraints: Trimmed length must be between 2 and 64 characters. |
Success response
| Field | Type | Details |
|---|---|---|
organization | object | Created organization record. |
organization.id | string | Organization identifier. |
organization.name | string | Organization display name. |
organization.slug | string | Resolved unique organization slug. |
organization.logo | string | null | Organization logo URL when set. |
starterAddressId | string | null | Starter inbox address ID when provisioning succeeds, otherwise null. |
seededSampleEmailCount | number | Number of sample emails seeded into the starter inbox during provisioning. |
starterInboxProvisioned | boolean | Whether starter inbox setup completed successfully during organization creation. |
warning | string | undefined | Present when the organization was created but starter inbox provisioning failed. |
Common error responses
| Status | Error | When it happens |
|---|---|---|
400 | Organization name must be between 2 and 64 characters | The request body is missing a valid name or the name is out of range. |
400 | EMAIL_DOMAINS is not configured | The backend cannot provision the required starter inbox domain. |
409 | Unable to create organization. Please try again. | Repeated slug collision retries are exhausted during organization creation. |
500 | Unable to create organization | Organization creation fails for another backend or auth-layer reason. |
Example request
curl -X POST "https://api.spinupmail.com/api/organizations" \
-H "Content-Type: application/json" \
-H "X-API-Key: spin_..." \
-d '{
"name": "QA Team"
}'Example response
{
"organization": {
"id": "org_abc123",
"name": "QA Team",
"slug": "qa-team",
"logo": null
},
"starterAddressId": "addr_123",
"seededSampleEmailCount": 2,
"starterInboxProvisioned": true
}List organization stats
/api/organizations/statsReturn aggregate per-organization counts for the authenticated user across organizations they belong to.
- Auth
- Authenticated endpoint. It is user-scoped rather than organization-scoped.
- Success
200
Request headers
| Header | Example | Requirement | Details |
|---|---|---|---|
Cookie session or X-API-Key | Cookie: <better-auth session> or X-API-Key: spin_... | Required | All documented product endpoints require an authenticated user session or a valid Better Auth API key. |
Success response
| Field | Type | Details |
|---|---|---|
items | Array<object> | One item per organization the current user belongs to. |
items[].organizationId | string | Organization identifier. |
items[].memberCount | number | Number of members in the organization. |
items[].addressCount | number | Number of email addresses in the organization. |
items[].emailCount | number | Number of stored emails in the organization. |
Common error responses
| Status | Error | When it happens |
|---|---|---|
401 | unauthorized | The request is not authenticated. |
403 | email verification required | The authenticated user has not verified their email address. |
Example request
curl "https://api.spinupmail.com/api/organizations/stats" \
-H "X-API-Key: spin_..."Example response
{
"items": [
{
"organizationId": "org_abc123",
"memberCount": 3,
"addressCount": 12,
"emailCount": 241
}
]
}Get email activity
/api/organizations/stats/email-activityReturn organization-scoped daily email counts grouped in the requested timezone.
- Auth
- Authenticated and organization-scoped. API key requests must include X-Org-Id.
- Success
200
- The days parameter is clamped to the range 1-30 and defaults to 14.
- Invalid timezone values return a 400 error instead of falling back silently.
Request headers
| Header | Example | Requirement | Details |
|---|---|---|---|
Cookie session or X-API-Key | Cookie: <better-auth session> or X-API-Key: spin_... | Required | All documented product endpoints require an authenticated user session or a valid Better Auth API key. |
X-Org-Id | org_abc123 | Optional | Required for API key requests on org-scoped endpoints. Session-cookie requests can use the active organization on the session instead. |
Query parameters
| Field | Type | Requirement | Details |
|---|---|---|---|
days | string | Optional | Requested number of recent days to include in the chart response. Default: Constraints: Clamped to 1-30. |
timezone | string | Optional | IANA timezone used to bucket daily counts, such as Europe/Istanbul or America/New_York. Default: |
Success response
| Field | Type | Details |
|---|---|---|
timezone | string | Resolved timezone used in the response. |
daily | Array<object> | One entry per day in the selected date window. |
daily[].date | string | Calendar day key in YYYY-MM-DD format. |
daily[].count | number | Number of emails received on that day. |
Common error responses
| Status | Error | When it happens |
|---|---|---|
400 | x-org-id header is required for api key usage | An API key request omits X-Org-Id. |
400 | active organization is required | A session request has no active organization and does not pass X-Org-Id. |
400 | invalid timezone | timezone is provided but is not a valid IANA timezone. |
401 | unauthorized | The request is not authenticated. |
403 | forbidden | The authenticated principal does not belong to the requested organization. |
Example request
curl --get "https://api.spinupmail.com/api/organizations/stats/email-activity" \
-H "X-API-Key: spin_..." \
-H "X-Org-Id: org_abc123" \
--data-urlencode "days=7" \
--data-urlencode "timezone=Europe/Istanbul"Example response
{
"timezone": "Europe/Istanbul",
"daily": [
{ "date": "2026-03-02", "count": 4 },
{ "date": "2026-03-03", "count": 2 },
{ "date": "2026-03-04", "count": 0 }
]
}Get email summary
/api/organizations/stats/email-summaryReturn organization-level aggregate email and attachment statistics used by dashboard summaries.
- Auth
- Authenticated and organization-scoped. API key requests must include X-Org-Id.
- Success
200
Request headers
| Header | Example | Requirement | Details |
|---|---|---|---|
Cookie session or X-API-Key | Cookie: <better-auth session> or X-API-Key: spin_... | Required | All documented product endpoints require an authenticated user session or a valid Better Auth API key. |
X-Org-Id | org_abc123 | Optional | Required for API key requests on org-scoped endpoints. Session-cookie requests can use the active organization on the session instead. |
Success response
| Field | Type | Details |
|---|---|---|
totalEmailCount | number | Total emails stored for the organization. |
attachmentCount | number | Total attachments stored for the organization. |
attachmentSizeTotal | number | Total attachment bytes stored for the organization. |
attachmentSizeLimit | number | Resolved per-organization attachment storage cap in bytes. |
topDomains | Array<object> | Most frequent sender domains. |
topDomains[].domain | string | Sender domain. |
topDomains[].count | number | Email count for that sender domain. |
busiestInboxes | Array<object> | Inboxes with the highest email counts. |
busiestInboxes[].addressId | string | Inbox identifier. |
busiestInboxes[].address | string | Inbox email address. |
busiestInboxes[].count | number | Email count for the inbox. |
dormantInboxes | Array<object> | Inboxes with no recent activity but existing records. |
dormantInboxes[].addressId | string | Inbox identifier. |
dormantInboxes[].address | string | Inbox email address. |
dormantInboxes[].createdAt | string | null | When the dormant inbox was created. Constraints: ISO 8601 timestamp when present. |
Common error responses
| Status | Error | When it happens |
|---|---|---|
400 | x-org-id header is required for api key usage | An API key request omits X-Org-Id. |
400 | active organization is required | A session request has no active organization and does not pass X-Org-Id. |
401 | unauthorized | The request is not authenticated. |
403 | forbidden | The authenticated principal does not belong to the requested organization. |
Example request
curl "https://api.spinupmail.com/api/organizations/stats/email-summary" \
-H "X-API-Key: spin_..." \
-H "X-Org-Id: org_abc123"Example response
{
"totalEmailCount": 241,
"attachmentCount": 19,
"attachmentSizeTotal": 483210,
"attachmentSizeLimit": 104857600,
"topDomains": [
{ "domain": "github.com", "count": 32 }
],
"busiestInboxes": [
{ "addressId": "addr_123", "address": "signup@spinupmail.dev", "count": 44 }
],
"dormantInboxes": [
{ "addressId": "addr_456", "address": "old-flow@spinupmail.dev", "createdAt": "2026-02-10T09:15:00.000Z" }
]
}
