Primitive Account API
Manage your account settings, storage, and webhook secret
Manage your account settings, storage, and webhook secret
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/primitive-account-api"
curl "https://apis.io/api/v1/apis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Primitive Account API
version: 1.0.0
description: Primitive is email infrastructure for AI agents.
contact:
name: Primitive
url: https://primitive.dev
license:
name: Proprietary
url: https://primitive.dev/terms
x-stability-level: stable
x-deprecation-policy: 'Breaking changes are announced at least 6 months in advance. Deprecated fields carry x-deprecated: true. The current stable version is v1.'
servers:
- url: https://api.primitive.dev/v1
description: Canonical API host (PRIMITIVE_API_BASE_URL). Carries every public API operation.
tags:
- name: Account
description: Manage your account settings, storage, and webhook secret
paths:
/account:
get:
operationId: getAccount
summary: Get account info
tags:
- Account
responses:
'200':
description: Account details
content:
application/json:
schema:
allOf:
- type: object
properties:
success:
type: boolean
const: true
required:
- success
- data
- type: object
properties:
data:
type: object
properties:
id:
type: string
format: uuid
email:
type: string
plan:
type: string
limits:
type: object
description: Plan-derived quota limits for an account.
properties:
storage_mb:
type: number
send_per_hour:
type: number
send_per_day:
type: number
api_per_minute:
type: number
webhooks_max_global:
type:
- number
- 'null'
webhooks_per_domain:
type: boolean
filters_per_domain:
type: boolean
spam_thresholds_per_domain:
type: boolean
required:
- storage_mb
- send_per_hour
- send_per_day
- api_per_minute
- webhooks_max_global
- webhooks_per_domain
- filters_per_domain
- spam_thresholds_per_domain
entitlements:
type: array
items:
type: string
description: 'Granted org entitlement keys (sorted). A headless caller reads its
capabilities here — e.g. an emailless agent seeing only
["send_mail", "send_to_known_addresses"] knows it is reply-only.
'
managed_inbox_address:
type:
- string
- 'null'
description: The managed inbox FQDN to reply as, or null if the org has no managed inbox.
created_at:
type: string
format: date-time
onboarding_completed:
type: boolean
onboarding_step:
type:
- string
- 'null'
stripe_subscription_status:
type:
- string
- 'null'
subscription_current_period_end:
type:
- string
- 'null'
format: date-time
subscription_cancel_at_period_end:
type:
- boolean
- 'null'
spam_threshold:
type:
- number
- 'null'
minimum: 0
maximum: 15
discard_content_on_webhook_confirmed:
type: boolean
webhook_secret_rotated_at:
type:
- string
- 'null'
format: date-time
required:
- id
- email
- plan
- limits
- entitlements
- managed_inbox_address
- created_at
- discard_content_on_webhook_confirmed
headers:
ratelimit-limit:
description: Maximum number of requests allowed in the current window.
schema:
type: integer
minimum: 1
example: 120
ratelimit-remaining:
description: Remaining requests in the current window.
schema:
type: integer
minimum: 0
example: 118
ratelimit-reset:
description: Unix timestamp (seconds) when the current window resets.
schema:
type: integer
example: 1700000060
ratelimit-policy:
description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
schema:
type: string
example: 120;w=60
'401':
$ref: '#/components/responses/Unauthorized'
description: Invalid or missing API key
'404':
$ref: '#/components/responses/NotFound'
description: Resource not found
security:
- BearerAuth: []
description: 'Retrieve the authenticated organization''s account: organization identity, current plan, usage, and onboarding state.'
patch:
operationId: updateAccount
summary: Update account settings
tags:
- Account
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
properties:
spam_threshold:
type:
- number
- 'null'
minimum: 0
maximum: 15
description: Global spam score threshold (0-15). Emails scoring above this are rejected. Set to null to disable.
discard_content_on_webhook_confirmed:
type: boolean
description: Whether to discard email content after the webhook endpoint confirms receipt.
minProperties: 1
responses:
'200':
description: Updated account
content:
application/json:
schema:
allOf:
- type: object
properties:
success:
type: boolean
const: true
required:
- success
- data
- type: object
properties:
data:
type: object
properties:
id:
type: string
format: uuid
email:
type: string
plan:
type: string
spam_threshold:
type:
- number
- 'null'
minimum: 0
maximum: 15
discard_content_on_webhook_confirmed:
type: boolean
required:
- id
- email
- plan
- discard_content_on_webhook_confirmed
headers:
ratelimit-limit:
description: Maximum number of requests allowed in the current window.
schema:
type: integer
minimum: 1
example: 120
ratelimit-remaining:
description: Remaining requests in the current window.
schema:
type: integer
minimum: 0
example: 118
ratelimit-reset:
description: Unix timestamp (seconds) when the current window resets.
schema:
type: integer
example: 1700000060
ratelimit-policy:
description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
schema:
type: string
example: 120;w=60
'400':
$ref: '#/components/responses/ValidationError'
description: Invalid request parameters
'401':
$ref: '#/components/responses/Unauthorized'
description: Invalid or missing API key
'404':
$ref: '#/components/responses/NotFound'
description: Resource not found
security:
- BearerAuth: []
description: Update the mutable account settings (such as display name and notification preferences) for the authenticated organization.
/account/storage:
get:
operationId: getStorageStats
summary: Get storage usage
tags:
- Account
responses:
'200':
description: Storage statistics
content:
application/json:
schema:
allOf:
- type: object
properties:
success:
type: boolean
const: true
required:
- success
- data
- type: object
properties:
data:
type: object
properties:
used_bytes:
type: integer
description: Total storage used in bytes
used_kb:
type: number
description: Total storage used in kilobytes (1 decimal)
used_mb:
type: number
description: Total storage used in megabytes (2 decimals)
quota_mb:
type: number
description: Storage quota in megabytes (based on plan)
percentage:
type: number
description: Percentage of quota used (1 decimal)
emails_count:
type: integer
description: Number of stored emails
required:
- used_bytes
- used_kb
- used_mb
- quota_mb
- percentage
- emails_count
headers:
ratelimit-limit:
description: Maximum number of requests allowed in the current window.
schema:
type: integer
minimum: 1
example: 120
ratelimit-remaining:
description: Remaining requests in the current window.
schema:
type: integer
minimum: 0
example: 118
ratelimit-reset:
description: Unix timestamp (seconds) when the current window resets.
schema:
type: integer
example: 1700000060
ratelimit-policy:
description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
schema:
type: string
example: 120;w=60
'401':
$ref: '#/components/responses/Unauthorized'
description: Invalid or missing API key
'404':
$ref: '#/components/responses/NotFound'
description: Resource not found
security:
- BearerAuth: []
description: Return stored-email storage usage and the remaining quota for the authenticated organization.
/account/webhook-secret:
get:
operationId: getWebhookSecret
summary: Get webhook signing secret
description: 'Returns the webhook signing secret for your account. If no
secret exists yet, one is generated automatically on first
access.
Signing is account-scoped, not per-endpoint. Every webhook
delivery from any of your registered endpoints is signed
with this single secret. Rotate via
`POST /account/webhook-secret/rotate`.
**Secret format**: the returned string looks base64-shaped
(e.g. `XNHBBW8VqoBjRfNs1tkZj11jTk...`) but is NOT base64.
Use it AS-IS as a UTF-8 string when computing HMAC over a
delivery body. Base64-decoding before HMAC will silently
produce mismatched signatures.
See the API-level "Webhook signing" section for the full
wire format (header name, signed string shape, hash algo,
tolerance) including a language-agnostic verification
recipe.'
tags:
- Account
responses:
'200':
description: Webhook secret
content:
application/json:
schema:
allOf:
- type: object
properties:
success:
type: boolean
const: true
required:
- success
- data
- type: object
properties:
data:
type: object
properties:
secret:
type: string
description: The webhook signing secret value
required:
- secret
headers:
ratelimit-limit:
description: Maximum number of requests allowed in the current window.
schema:
type: integer
minimum: 1
example: 120
ratelimit-remaining:
description: Remaining requests in the current window.
schema:
type: integer
minimum: 0
example: 118
ratelimit-reset:
description: Unix timestamp (seconds) when the current window resets.
schema:
type: integer
example: 1700000060
ratelimit-policy:
description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
schema:
type: string
example: 120;w=60
'401':
$ref: '#/components/responses/Unauthorized'
description: Invalid or missing API key
'404':
$ref: '#/components/responses/NotFound'
description: Resource not found
security:
- BearerAuth: []
/account/webhook-secret/rotate:
post:
operationId: rotateWebhookSecret
summary: Rotate webhook signing secret
description: 'Generates a new webhook signing secret, replacing the current one.
Rate limited to once per 60 minutes.'
tags:
- Account
responses:
'200':
description: New webhook secret
content:
application/json:
schema:
allOf:
- type: object
properties:
success:
type: boolean
const: true
required:
- success
- data
- type: object
properties:
data:
type: object
properties:
secret:
type: string
description: The webhook signing secret value
required:
- secret
headers:
ratelimit-limit:
description: Maximum number of requests allowed in the current window.
schema:
type: integer
minimum: 1
example: 120
ratelimit-remaining:
description: Remaining requests in the current window.
schema:
type: integer
minimum: 0
example: 118
ratelimit-reset:
description: Unix timestamp (seconds) when the current window resets.
schema:
type: integer
example: 1700000060
ratelimit-policy:
description: Rate-limit policy in `limit;w=seconds` format, e.g. `120;w=60`.
schema:
type: string
example: 120;w=60
'400':
$ref: '#/components/responses/ValidationError'
description: Invalid request parameters
'401':
$ref: '#/components/responses/Unauthorized'
description: Invalid or missing API key
'404':
$ref: '#/components/responses/NotFound'
description: Resource not found
'429':
$ref: '#/components/responses/RateLimited'
description: Rate limit exceeded
security:
- BearerAuth: []
parameters:
- name: Idempotency-Key
in: header
required: false
description: Optional client-supplied idempotency key. Retrying a request with the same key returns the original result instead of performing the action a second time; if omitted the server derives one from the canonical payload hash. Safe to retry network failures without duplicating side effects.
schema:
type: string
minLength: 1
maxLength: 255
components:
responses:
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
success: false
error:
code: not_found
message: Resource not found
Unauthorized:
description: Invalid or missing API key
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
success: false
error:
code: unauthorized
message: Invalid or missing API key
ValidationError:
description: Invalid request parameters
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
success: false
error:
code: validation_error
message: Invalid domain format
RateLimited:
description: Rate limit exceeded
headers:
Retry-After:
schema:
type: integer
description: Seconds to wait before retrying
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
example:
success: false
error:
code: rate_limit_exceeded
message: Rate limit exceeded
schemas:
ErrorResponse:
type: object
properties:
success:
type: boolean
const: false
error:
type: object
properties:
code:
type: string
enum:
- unauthorized
- forbidden
- not_found
- validation_error
- rate_limit_exceeded
- internal_error
- conflict
- mx_conflict
- outbound_disabled
- cannot_send_from_domain
- recipient_not_allowed
- outbound_key_missing
- outbound_unreachable
- outbound_key_invalid
- outbound_capacity_exhausted
- outbound_response_malformed
- outbound_relay_failed
- discard_not_enabled
- inbound_not_repliable
- search_timeout
- authorization_pending
- slow_down
- access_denied
- expired_token
- invalid_device_code
- invalid_signup_code
- invalid_signup_token
- invalid_verification_code
- email_delivery_failed
- clerk_signup_failed
- no_orgs_for_user
- org_not_accessible
- feature_disabled
- memory_conflict
- developer_usage_credit_exhausted
- no_payout_address
- ownership_proof_failed
- payment_verification_failed
- payment_declined
- challenge_expired
- settlement_failed
- template_not_installable
- scaffold_only
- invalid_variables
- unknown_secrets
- missing_secrets
- no_inbound_domain
- domain_cannot_send
- address_taken
- route_cap_reached
- name_exhausted
message:
type: string
details:
type: object
description: 'Optional structured data that callers can inspect to recover
from the error. The fields present depend on `code`. Additional
keys may be added over time without a major-version bump.
'
additionalProperties: true
properties:
mx_conflict:
type: object
description: Present when `code == mx_conflict`.
required:
- provider_name
- suggested_subdomain
properties:
provider_name:
type: string
description: Human-readable name of the detected mailbox provider (e.g. "Google Workspace").
suggested_subdomain:
type: string
description: Subdomain to try instead (e.g. "mail" for `mail.example.com`).
required_entitlements:
type: array
items:
type: string
description: Entitlements that would allow a denied send when no recipient-scope gate was granted.
sent_email_id:
type: string
description: ID of the persisted sent-email attempt associated with the error.
content_hash:
type: string
description: Content hash of the original request on idempotency cache-hit errors.
client_idempotency_key:
type: string
description: Effective idempotency key associated with the original request.
gates:
type: array
items:
$ref: '#/components/schemas/GateDenial'
description: Structured per-gate denial detail for recipient-scope send-mail failures.
request_id:
type: string
description: Server-issued request identifier for support and tracing.
required:
- code
- message
required:
- success
- error
GateDenial:
type: object
properties:
name:
type: string
enum:
- send_to_confirmed_domains
- send_to_known_addresses
description: Public recipient-scope gate name that denied the send.
reason:
type: string
enum:
- domain_not_confirmed
- recipient_unauthenticated
- recipient_not_known
description: Stable machine-readable denial reason.
message:
type: string
description: Human-readable explanation of the gate denial.
subject:
type: string
description: Domain or address the gate evaluated.
fix:
$ref: '#/components/schemas/GateFix'
docs_url:
type: string
description: Public docs URL with more context.
required:
- name
- reason
- message
- subject
GateFix:
type: object
properties:
action:
type: string
enum:
- confirm_domain
- sender_must_fix_authentication
- wait_for_inbound
description: Suggested next action for the caller.
subject:
type: string
description: Entity the action applies to.
required:
- action
- subject
securitySchemes:
BearerAuth:
type: http
scheme: bearer
description: 'API key with `prim_` prefix or OAuth access token with `prim_oat_` prefix: `Authorization: Bearer <token>`. Access is governed by the caller''s organization role (`owner`, `admin`, or `member`): API keys always act at `member` level regardless of who created them, and OAuth access tokens act with the authorizing user''s current organization role, resolved per request. Every operation in this spec is available to organization members; billing and organization administration are owner/admin actions performed in the dashboard and are not part of this API.'
DownloadToken:
type: apiKey
in: query
name: token
description: Signed download token provided in webhook payloads