Primitive Templates API
Public Function template registry reads used to browse installable agent templates.
Public Function template registry reads used to browse installable agent templates.
openapi: 3.1.0
info:
title: Primitive Account Templates API
version: 1.0.0
description: "Primitive is email infrastructure for AI agents. The Primitive API lets you manage domains, emails, webhook endpoints,\nfilters, and account settings programmatically.\n\n## Authentication\n\nMost endpoints require a Bearer token in the `Authorization` header:\n\n```\nAuthorization: Bearer prim_<your_api_key>\nAuthorization: Bearer prim_oat_<oauth_access_token>\n```\n\nAPI keys and OAuth access tokens are org-scoped. Create and manage them in your dashboard\nunder Settings > API Keys. CLI login plus CLI/agent signup endpoints\nexplicitly declare `security: []`; they do not require an API key because\nthey are used to create OAuth CLI sessions.\n\n## Rate Limiting\n\nThe API enforces a sliding window rate limit of **120 requests per\n60 seconds** per organization. When exceeded, the API returns `429`\nwith a `Retry-After` header indicating how many seconds to wait.\n\n## Pagination\n\nList endpoints use cursor-based pagination. Responses include a\n`meta` object with `total`, `limit`, and `cursor` fields. Pass the\n`cursor` value as a query parameter to fetch the next page. When\n`cursor` is `null`, there are no more results.\n\n## Response Format\n\nAll responses use a consistent envelope:\n\n```json\n{\n \"success\": true,\n \"data\": { ... },\n \"meta\": { \"total\": 42, \"limit\": 50, \"cursor\": \"...\" }\n}\n```\n\nErrors follow the same pattern:\n\n```json\n{\n \"success\": false,\n \"error\": { \"code\": \"not_found\", \"message\": \"Email not found\" }\n}\n```\n\n## Webhook signing\n\nOutbound webhook deliveries (configured via the `endpoints` API)\nare signed so receivers can verify they came from Primitive and\nhave not been tampered with in transit. The signing scheme is\ndeliberately simple so it can be reimplemented in any language\nin a few lines. The Node SDK's `verifyWebhookSignature` helper\nis the reference implementation; the wire details below let you\nwrite a verifier in Python, Go, Ruby, etc. without reading our\nsource.\n\n**Header**: `Primitive-Signature: t=<unix-seconds>,v1=<hex>`\n\nA legacy `MyMX-Signature` header is also sent on every delivery\nwith the same value, retained for back-compatibility with\nintegrations written before the rename. New code should read\n`Primitive-Signature`.\n\n**Signed string**: `${timestamp}.${rawBody}` where `timestamp`\nis the Unix-seconds integer from the `t=` parameter and\n`rawBody` is the exact bytes of the HTTP request body BEFORE\nany JSON decoding. Verify against the raw body, not a\nre-serialized parse, or you will silently mismatch on\ninsignificant whitespace.\n\n**Signature**: HMAC-SHA256 of the signed string, hex-encoded\n(lowercase). Use the account's webhook secret as the HMAC key,\nas a UTF-8 byte sequence.\n\n**Secret**: returned by `GET /account/webhook-secret`. The\nstring looks base64-shaped (e.g. `XNHBBW8VqoBjRfNs1tkZj11jTk...`)\nbut is NOT base64; use it AS-IS as a UTF-8 string for the HMAC\nkey. Base64-decoding before HMAC will silently produce\nmismatched signatures.\n\n**Tolerance**: by convention, reject deliveries whose `t=`\ntimestamp is more than 5 minutes off your wall-clock to defend\nagainst replay attacks. The Node SDK's helper enforces this by\ndefault.\n\n**Verification recipe** (any language):\n\n```\n1. Read the raw HTTP body (do not parse).\n2. Read `Primitive-Signature: t=<ts>,v1=<sig>`.\n3. Reject if abs(now - ts) > 300 seconds.\n4. expected = HMAC_SHA256_hex(secret_utf8, f\"{ts}.{rawBody}\")\n5. Constant-time compare expected to sig. Reject if not equal.\n```\n\nFor Node, use `verifyWebhookSignature` from\n`@primitivedotdev/sdk/webhook` (or the higher-level\n`handleWebhook` helper if you want a one-liner). For other\nlanguages, the recipe above is everything you need.\n\nTest deliveries: `POST /endpoints/{id}/test` triggers a fake\ndelivery to your endpoint URL, signed with your real account\nsecret, so you can confirm verification end-to-end without\nneeding real inbound mail. The test response carries the exact\n`signature` header value sent on the wire so you can compare\nstrings directly.\n\n\n## Errors\n\nEvery error response is the same JSON envelope (`{ \"success\": false, \"error\": { \"code\", \"message\" } }`), served as `application/json` with HTTP status codes, following the RFC 7807 problem-details shape. The `error.code` is a stable machine-readable string and `error.message` is human-readable.\n\n## Authorization and roles\n\nAccess is governed by organization role-based access control. Every organization member holds one of three roles — `owner`, `admin`, or `member` — and a credential inherits a role. **API keys** always act at `member` level, regardless of the role of the user who created them, so an API key can never perform owner- or admin-only actions. **OAuth access tokens** act with the authorizing user's current organization role, resolved on each request. Every operation in this spec is part of the member-level surface, so any valid credential can call it. Organization administration that is not part of this API — billing and organization settings — requires an `owner` or `admin` and is performed in the dashboard. Fine-grained per-key scopes (e.g. a send-only or read-only key) are on the roadmap; today the role model is the unit of access control.\n\n## Versioning\n\nThe current stable API is **v1**. All endpoints are served under `/v1/` and are covered by a backward-compatibility guarantee: existing fields and status codes will not change without a deprecation notice.\n\nBreaking changes are announced at least 6 months in advance via changelog and email. Deprecated operations and fields are marked `x-deprecated: true` in the spec and carry a plain-English description of the replacement. The `v1` path prefix is guaranteed stable indefinitely; backward-compatible additions (new optional fields, new endpoints) may be made at any time without a version bump."
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: Templates
description: Public Function template registry reads used to browse installable agent templates.
paths:
/templates:
get:
operationId: listTemplates
summary: List function templates
description: List approved Function templates available for browsing and installation. Results are cacheable and paginated with `data.next_cursor`.
tags:
- Templates
security: []
parameters:
- name: q
in: query
required: false
description: Search templates by title, summary, or slug.
schema:
type: string
minLength: 1
- name: tag
in: query
required: false
description: Filter templates by an exact tag.
schema:
type: string
minLength: 1
- name: cursor
in: query
required: false
description: Cursor from a previous response `data.next_cursor` value.
schema:
type: string
- name: limit
in: query
required: false
description: Maximum number of templates to return. The server caps this at 100.
schema:
type: integer
minimum: 1
maximum: 100
default: 50
responses:
'200':
description: Paginated template registry summaries.
content:
application/json:
schema:
allOf:
- type: object
properties:
success:
type: boolean
const: true
required:
- success
- data
- type: object
properties:
data:
type: object
properties:
items:
type: array
items:
type: object
properties:
id:
type: string
format: uuid
slug:
type: string
pattern: ^[a-z0-9][a-z0-9_-]{0,62}$
description: Stable template slug used in template URLs and install commands.
title:
type: string
summary:
type: string
author:
type: object
additionalProperties: false
properties:
id:
type: string
minLength: 1
name:
type: string
minLength: 1
url:
type: string
format: uri
required:
- id
- name
tags:
type: array
items:
type: string
verified:
type: boolean
install_count:
type: integer
minimum: 0
github_repo:
type: string
pattern: ^[A-Za-z0-9-]+/[A-Za-z0-9_.-]+$
description: GitHub repository in owner/repo form.
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
required:
- id
- slug
- title
- summary
- author
- tags
- verified
- install_count
- github_repo
- created_at
- updated_at
next_cursor:
type:
- string
- 'null'
description: Cursor to pass as the next `cursor` query value, or null when there are no more templates.
required:
- items
- next_cursor
'400':
$ref: '#/components/responses/ValidationError'
description: Invalid request parameters
'429':
$ref: '#/components/responses/RateLimited'
description: Rate limit exceeded
/templates/{id}:
get:
operationId: getTemplate
summary: Get a function template
description: Fetch one approved Function template by slug, including its manifest snapshot and README. The stored source files used for install are not returned.
tags:
- Templates
security: []
parameters:
- name: id
in: path
required: true
description: Template slug from the template manifest.
schema:
type: string
pattern: ^[a-z0-9][a-z0-9_-]{0,62}$
description: Template slug.
responses:
'200':
description: Template registry detail.
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
slug:
type: string
pattern: ^[a-z0-9][a-z0-9_-]{0,62}$
description: Stable template slug used in template URLs and install commands.
title:
type: string
summary:
type: string
author:
type: object
additionalProperties: false
properties:
id:
type: string
minLength: 1
name:
type: string
minLength: 1
url:
type: string
format: uri
required:
- id
- name
tags:
type: array
items:
type: string
verified:
type: boolean
install_count:
type: integer
minimum: 0
github_repo:
type: string
pattern: ^[A-Za-z0-9-]+/[A-Za-z0-9_.-]+$
description: GitHub repository in owner/repo form.
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
description:
type:
- string
- 'null'
github_sha:
type: string
github_path:
type:
- string
- 'null'
manifest:
type: object
additionalProperties: false
properties:
schemaVersion:
type: integer
const: 1
id:
type: string
pattern: ^[a-z0-9][a-z0-9_-]{0,62}$
description: Stable template slug from the manifest.
title:
type: string
minLength: 1
summary:
type: string
minLength: 1
description:
type: string
author:
type: object
additionalProperties: false
properties:
id:
type: string
minLength: 1
name:
type: string
minLength: 1
url:
type: string
format: uri
required:
- id
- name
tags:
type: array
items:
type: string
minLength: 1
default: []
source:
oneOf:
- type: object
additionalProperties: false
properties:
mode:
type: string
const: managed-build
dir:
type: string
minLength: 1
default: .
required:
- mode
- dir
- type: object
additionalProperties: false
properties:
mode:
type: string
const: bundle
file:
type: string
minLength: 1
required:
- mode
- file
install:
type: object
additionalProperties: false
properties:
mode:
type: string
enum:
- deploy
- scaffold
editFiles:
type: array
items:
type: string
minLength: 1
default: []
reason:
type: string
default: ''
required:
- mode
- editFiles
- reason
secrets:
type: array
items:
type: object
additionalProperties: false
properties:
key:
type: string
pattern: ^[A-Z_][A-Z0-9_]*$
required:
type: boolean
default: true
description:
type: string
required:
- key
- required
default: []
secretGroups:
type: array
items:
type: object
additionalProperties: false
properties:
keys:
type: array
minItems: 2
items:
type: string
pattern: ^[A-Z_][A-Z0-9_]*$
min:
type: integer
minimum: 1
default: 1
description:
type: string
required:
- keys
- min
default: []
variables:
type: array
items:
type: object
additionalProperties: false
properties:
key:
type: string
minLength: 1
prompt:
type: string
minLength: 1
default:
type: string
file:
type: string
minLength: 1
type:
type: string
enum:
- string
- select
- url
- email
default: string
options:
type: array
items:
type: string
minLength: 1
validation:
type: object
additionalProperties: false
properties:
pattern:
type: string
minLength: 1
maxLength:
type: integer
minimum: 1
required:
- key
- prompt
- type
default: []
consumesVendors:
type: array
items:
type: object
additionalProperties: false
properties:
slug:
type: string
minLength: 1
required:
type: boolean
default: true
required:
- slug
default: []
setup:
type: object
additionalProperties: false
properties:
agent:
type: string
minLength: 1
prompt:
type: string
minLength: 1
produces:
type: array
items:
type: string
minLength: 1
default: []
required:
- agent
- prompt
- produces
postInstall:
type: string
required:
- schemaVersion
- id
- title
- summary
- author
- tags
- source
- install
- secrets
- secretGroups
- variables
readme:
type:
- string
- 'null'
status:
type: string
enum:
- pending
- approved
- rejected
required:
- id
- slug
- title
- summary
- author
- tags
- verified
- install_count
- github_repo
- created_at
- updated_at
- description
- github_sha
- github_path
- manifest
- readme
- status
'400':
$ref: '#/components/responses/ValidationError'
description: Invalid request parameters
'404':
$ref: '#/components/responses/NotFound'
description: Resource not found
'429':
$ref: '#/components/responses/RateLimited'
description: Rate limit exceeded
/templates/{id}/install:
post:
operationId: installTemplate
summary: Install a function template
description: Start a one-shot deploy of an approved deploy-mode Function template. The response returns an install record immediately; poll `GET /templates/installs/{id}` for progress.
tags:
- Templates
security:
- BearerAuth: []
parameters:
- name: id
in: path
required: true
description: Template slug from the template manifest.
schema:
type: string
pattern: ^[a-z0-9][a-z0-9_-]{0,62}$
description: Template slug.
- 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
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
properties:
address:
type: string
pattern: ^[a-z0-9][a-z0-9._-]{0,63}$
description: Localpart to claim on the selected inbound domain. Defaults to the template slug.
domain:
type: string
minLength: 3
description: Org domain name to claim the address on. Defaults to the org's newest active domain.
variables:
type: object
additionalProperties:
type: string
description: Template variable values keyed by manifest variable key.
secrets:
type: object
additionalProperties:
type: string
description: Secret values keyed by manifest secret key.
responses:
'201':
description: Template install started.
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
template_slug:
type: string
pattern: ^[a-z0-9][a-z0-9_-]{0,62}$
description: Template slug for this install.
state:
type: string
enum:
- deploying
- connecting
- bound
- tested
- deploy_failed
- bind_failed
- test_failed
address:
type:
- string
- 'null'
function_id:
type:
- string
- 'null'
format: uuid
error:
type:
- string
- 'null'
created_at:
type: string
format: date-time
updated_at:
type: string
format: date-time
required:
- id
- template_slug
- state
- address
- function_id
- error
- created_at
- updated_at
'400':
$ref: '#/components/responses/ValidationError'
description: Invalid request parameters
'401':
$ref: '#/components/responses/Unauthorized'
description: Invalid or missing API key
'403':
$ref: '#/components/responses/Forbidden'
description: Authenticated caller lacks permission for the operation
'404':
$ref: '#/components/responses/NotFound'
description: Resource not found
'409':
$ref: '#/components/responses/Conflict'
description: The request conflicts with the current state of the resource
'422':
$ref: '#/components/responses/UnprocessableEntity'
description: 'The request was well-formed but could not be processed. For Payments
this covers a missing payout address, a failed payment verification, a
spend-policy decline, or an expired challenge; `error.code` distinguishes
them.
'
'429':
$ref: '#/components/responses/RateLimited'
description: Rate limit exceeded
/templates/installs/{id}:
get:
operationId: getTemplateInstall
summary: Get template install status
description: Fetch the current state of a template install. Reads may advance the self-test phase when a reply effect has been observed.
tags:
- Templates
security:
- BearerAuth: []
parameters:
- name: id
in: path
required: true
description: Template install UUID.
schema:
type: string
format: uuid
pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$
responses:
'200':
description: Current template install status.
content:
application/json:
schema:
allOf:
- type: object
properties:
success:
type: boolean
const: true
re
# --- truncated at 32 KB (44 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/primitive/refs/heads/main/openapi/primitive-templates-api-openapi.yml