# Postforge: the complete developer docs
> Postforge is the open-source ecommerce email platform: it connects to your store (Shopify first), brings in customers, orders, products and on-site events, and runs campaigns and automations on that data, sent through your own Resend or Amazon SES account. Every feature is in a typed API with an MCP server, so AI agents can set it up and operate it; people approve every change.
Every page of the developer docs (https://postforge.dev/developers) as Markdown: the guides, then the API reference, one operation per section. The index of everything else is https://postforge.dev/llms.txt; the full schemas are in the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
---
# API reference
Every operation of the Postforge REST API, its parameters and its answers, generated from the OpenAPI spec.
The reference is generated from the API's own contracts, so it always matches what the API does.
Operations are grouped by resource in the sidebar; each page shows its parameters, request body,
answers and code samples.
- **Base URL:** `https://api.postforge.dev/v1`
- **Spec:** [`openapi.json`](https://api.postforge.dev/v1/openapi.json) (OpenAPI 3.1): import it
into your HTTP client or generate a typed client from it
- **Authentication:** operations marked with the `apiKey` scheme accept a workspace API key with
the scope they name; the rest need a person signed in to the dashboard
([Authentication](/developers/authentication))
- **Errors:** every operation answers [problem details](/developers/problems) on failure
Source: https://postforge.dev/developers/api
---
# Authentication
Postforge workspace API keys, their scopes, and how to rotate and revoke them.
Postforge's API accepts workspace API keys as bearer tokens:
```http
Authorization: Bearer pf_…
```
## Workspace keys
- A key belongs to **one workspace**; every request with it works in that workspace only.
- Keys are stored as a SHA-256 hash and shown once, when created or rotated.
- Everyone in the workspace sees the list under **Developers → API keys**; owners and admins
create, rotate and revoke them. The list shows when each key was last used.
## Scopes
Each key has one scope, and each scope includes the ones before it:
| Scope | Can |
| --- | --- |
| `read` | Read data: profiles, orders, products, coupons, definitions, … |
| `propose` | Everything `read` can, plus create [change proposals](/developers/definitions) that a person approves |
| `write` | Everything `propose` can, plus create, change and delete |
Operations that manage the team, API keys or workspace ownership never accept keys: they need a
person signed in to the dashboard. The [API reference](/developers/api) marks which operations
accept keys; a key with too small a scope gets a `403` problem with code `scope`.
## Rotating and revoking
- **Rotate** gives a key a new secret with the same name and scope; the old secret stops working
at once.
- **Revoke** stops a key for good; it stays listed so you can see what it was.
Rotate a key whenever it may have leaked, and give every integration its own key so you can
revoke one without breaking the others.
## AI clients
AI clients will connect to the [MCP server](/developers/mcp) with OAuth 2.1, acting with the
signed-in person's role in the workspace they choose. Until it is live, agents use a workspace
key; a `propose` key keeps every change behind a person's approval.
Source: https://postforge.dev/developers/authentication
---
# Definitions and schemas
Postforge's typed definitions (automations, segments, templates, campaigns), their JSON Schemas, versions and change proposals.
Automations, segments, templates, campaigns and the brand kit are **definitions**: typed JSON
documents with immutable versions. The same schema validates the API, drives the dashboard's
editor and is published for agents.
## Schemas
- [`GET /v1/schemas`](https://api.postforge.dev/v1/schemas) lists every kind with its resource,
label and current `schemaVersion`.
- `GET /v1/schemas/{kind}` answers the JSON Schema of one kind.
Validate a document against its schema before you send it; the API refuses an invalid one with a
[validation problem](/developers/problems/validation) listing each invalid path.
## Versions
Every save creates a new, immutable version with its author. A definition has a draft and,
once published, a published version; you can read any earlier version and restore it.
## Change proposals
A `propose` key, the in-app assistant and AI clients never change a definition directly: they
create a **change proposal** with the proposed document, a summary and a diff from the current
draft. A person approves or rejects it in the dashboard. That keeps every change an AI drafts
behind a human decision.
Source: https://postforge.dev/developers/definitions
---
# Idempotency
How Postforge API writes use Idempotency-Key so a retry never applies a change twice.
Networks fail: a request can time out after Postforge already did the work. Send an
`Idempotency-Key` header with every write, and retry with the **same key** until you get an answer.
```http
PUT /v1/profiles/0192f3a4-5b6c-7d8e-9f01-23456789abcd/consent/email
Idempotency-Key: 7f9c2b8e-3d41-4f0a-9a6e-2c1d5e8b4a10
```
## How it works
- A key is 1 to 255 characters; a UUID per logical change is the simplest choice.
- The first request runs. Its answer, success or refusal, is kept for **24 hours**.
- A retry with the same key and the same request gets that answer back, with
`Idempotent-Replayed: true`, without running again.
- While the first request is still running, a retry gets `409` with code `idempotency_key_in_use`:
wait a moment and retry. A request that died stops blocking its key after 5 minutes.
- The same key with a different request (another operation or body) gets `422` with code
`idempotency_key_reused`. Use a new key for a new change.
- If the request failed unexpectedly (a `5xx`), the key is freed, so a retry runs again.
Keys are scoped to the workspace: every API key and person in it shares one set of keys, so use a
fresh UUID per change and two integrations can never collide.
## Where it doesn't apply
`GET` requests are safe to repeat and ignore the header. A few writes whose answer holds a secret
shown once (creating or rotating an API key) refuse it with code `idempotency_key_not_supported`,
because that answer can't be stored for a replay.
Records that come from outside (orders and customers from a store, webhooks) are also written once
by their own natural keys, so a platform resending them never creates duplicates.
Source: https://postforge.dev/developers/idempotency
---
# Postforge developers
Build on Postforge, the open-source ecommerce email platform, with its REST API.
Everything the Postforge dashboard does goes through the same REST API you can call: workspaces,
stores, profiles, orders, products, coupons, feeds and the typed definitions behind automations,
segments, templates and campaigns. People, API keys and AI clients get the same operations with
the same permissions.
## At a glance
| | |
| --- | --- |
| Base URL | `https://api.postforge.dev/v1` |
| Format | JSON in and out, camelCase |
| Authentication | `Authorization: Bearer pf_…` (a workspace API key) |
| Errors | [RFC 9457 problem details](/developers/problems) |
| Retries | [`Idempotency-Key`](/developers/idempotency) on every write |
| Limits | [600 requests a minute per key](/developers/rate-limits) |
| Spec | [`openapi.json`](https://api.postforge.dev/v1/openapi.json) (OpenAPI 3.1) |
| Schemas | [`/v1/schemas`](https://api.postforge.dev/v1/schemas) (JSON Schema for definitions) |
## For AI agents
Postforge is built for AI to set up and operate. Agents read
[llms.txt](https://postforge.dev/llms.txt) (the index) or
[llms-full.txt](https://postforge.dev/llms-full.txt) (every docs page in one file), add `.md` to
any docs page for its Markdown, call the API with a workspace key, and write
definitions against their [JSON Schemas](/developers/definitions); changes made with a `propose`
key become proposals a person approves. An [MCP server](/developers/mcp) is coming before launch.
Until version 1 the API can change without notice. From launch, `/v1` follows the
[versioning policy](/developers/versioning).
Source: https://postforge.dev/developers
---
# MCP server
Postforge's Model Context Protocol server for Claude, Codex, Cursor and other AI clients, coming before launch.
The MCP server is not live yet. Until it is, AI agents use the REST API with a workspace API key.
Postforge's MCP server will run at `https://api.postforge.dev/mcp` over Streamable HTTP. Claude,
Claude Code, Codex, Cursor and any other MCP client will connect with OAuth 2.1: you sign in, pick
a workspace, and the client acts with your role in it, revocable at any time.
Its tools come from the same operations as the [REST API](/developers/api), with the same
permissions, plus a few made for AI: reading a definition's schema, proposing a change, rendering
a template, dry-running an automation and explaining a segment or an order's attribution. Changes
to definitions from AI clients always become [proposals](/developers/definitions) a person approves.
Source: https://postforge.dev/developers/mcp
---
# business-rule
422 This isn’t allowed: what the business-rule problem from the Postforge API means and what to do.
| Type | Status | Title |
| --- | --- | --- |
| `https://postforge.dev/developers/problems/business-rule` | 422 | This isn’t allowed |
The request is well formed, but a rule of Postforge refuses it: for example removing the last way to sign in, or reusing an idempotency key for a different request.
## Codes
Common `code` values (new ones can appear):
- `idempotency_key_reused`: this `Idempotency-Key` was used for a different request
- `feed_unreadable`: the feed’s URL can’t be read as a feed
## What to do
Read `detail`: it says which rule applies and what to do instead.
Source: https://postforge.dev/developers/problems/business-rule
---
# conflict
409 This conflicts with the current state: what the conflict problem from the Postforge API means and what to do.
| Type | Status | Title |
| --- | --- | --- |
| `https://postforge.dev/developers/problems/conflict` | 409 | This conflicts with the current state |
The request is valid but clashes with the resource’s current state: a stale version, a duplicate, or another request with the same idempotency key still running.
## Codes
Common `code` values (new ones can appear):
- `idempotency_key_in_use`: the first request with this key is still running
- `store_not_connected`: the store isn’t connected to Postforge
## What to do
Read the resource again and decide; for `idempotency_key_in_use`, wait a moment and retry with the same key.
Source: https://postforge.dev/developers/problems/conflict
---
# forbidden
403 You can’t do this: what the forbidden problem from the Postforge API means and what to do.
| Type | Status | Title |
| --- | --- | --- |
| `https://postforge.dev/developers/problems/forbidden` | 403 | You can’t do this |
The caller is known but isn’t allowed to run this operation here: the key’s scope is too small, the person’s role is too low, or the workspace requires two-factor sign-in.
## Codes
Common `code` values (new ones can appear):
- `scope`: the operation needs a key with a larger scope, or doesn’t accept keys at all
- `role`: only owners, or owners and admins, can do this
- `two_factor_required`: the workspace requires two-factor sign-in
## What to do
Use a key with the right scope, or ask a workspace owner or admin. Retrying doesn’t help.
Source: https://postforge.dev/developers/problems/forbidden
---
# gone
410 No longer available: what the gone problem from the Postforge API means and what to do.
| Type | Status | Title |
| --- | --- | --- |
| `https://postforge.dev/developers/problems/gone` | 410 | No longer available |
The resource existed but can’t be used any more: for example an invitation that was withdrawn or has expired.
## What to do
Don’t retry. Ask for a new link or invitation.
Source: https://postforge.dev/developers/problems/gone
---
# Problems
The errors the Postforge API answers, as RFC 9457 problem details, and what to do about each.
Every error the API answers is a problem details object ([RFC 9457](https://www.rfc-editor.org/rfc/rfc9457)) with `Content-Type: application/problem+json`:
```json
{
"type": "https://postforge.dev/developers/problems/forbidden",
"title": "You can’t do this",
"status": 403,
"detail": "This needs a key with the write scope.",
"code": "scope"
}
```
| Field | Meaning |
| --- | --- |
| `type` | The kind of problem: a link to its page below |
| `title` | The kind in words; the same for every problem of a kind |
| `status` | The HTTP status |
| `detail` | What went wrong and what to do, written for people: safe to show in your UI |
| `code` | A finer reason within the kind, for your code to branch on |
| `errors` | Validation only: one `{ path, message }` per invalid field |
Branch on `type` and `code`, show `detail`. New codes can appear within a kind at any time, so treat an unknown `code` like its kind.
| Kind | Status | Title |
| --- | --- | --- |
| [`validation`](/developers/problems/validation) | 400 | The request isn’t valid |
| [`unauthenticated`](/developers/problems/unauthenticated) | 401 | Sign in to continue |
| [`forbidden`](/developers/problems/forbidden) | 403 | You can’t do this |
| [`not-found`](/developers/problems/not-found) | 404 | Not found |
| [`conflict`](/developers/problems/conflict) | 409 | This conflicts with the current state |
| [`gone`](/developers/problems/gone) | 410 | No longer available |
| [`business-rule`](/developers/problems/business-rule) | 422 | This isn’t allowed |
| [`rate-limited`](/developers/problems/rate-limited) | 429 | Too many requests |
| [`internal`](/developers/problems/internal) | 500 | Something went wrong |
Source: https://postforge.dev/developers/problems
---
# internal
500 Something went wrong: what the internal problem from the Postforge API means and what to do.
| Type | Status | Title |
| --- | --- | --- |
| `https://postforge.dev/developers/problems/internal` | 500 | Something went wrong |
Postforge failed unexpectedly. The failure is logged and looked at; it isn’t caused by your request.
## What to do
Retry with backoff. Writes sent with an `Idempotency-Key` are safe to retry: a failed request frees its key.
Source: https://postforge.dev/developers/problems/internal
---
# not-found
404 Not found: what the not-found problem from the Postforge API means and what to do.
| Type | Status | Title |
| --- | --- | --- |
| `https://postforge.dev/developers/problems/not-found` | 404 | Not found |
The resource doesn’t exist, or it belongs to another workspace. Postforge answers both the same way, so ids from other workspaces reveal nothing.
## What to do
Check the id and that the key belongs to the workspace you expect.
Source: https://postforge.dev/developers/problems/not-found
---
# rate-limited
429 Too many requests: what the rate-limited problem from the Postforge API means and what to do.
| Type | Status | Title |
| --- | --- | --- |
| `https://postforge.dev/developers/problems/rate-limited` | 429 | Too many requests |
The caller used up one of its [rate limit](/developers/rate-limits) quotas.
## Extra fields
- `Retry-After`: Seconds to wait before trying again (a header).
- `RateLimit`: The quota that ran out, with `r=0` (a header).
## Codes
Common `code` values (new ones can appear):
- `standard_rate_limit`: 600 requests a minute
- `expensive_rate_limit`: 60 expensive operations a minute
## What to do
Wait at least `Retry-After` seconds, with some random jitter, then retry. Spread bulk work out.
Source: https://postforge.dev/developers/problems/rate-limited
---
# unauthenticated
401 Sign in to continue: what the unauthenticated problem from the Postforge API means and what to do.
| Type | Status | Title |
| --- | --- | --- |
| `https://postforge.dev/developers/problems/unauthenticated` | 401 | Sign in to continue |
The request has no valid credentials: no `Authorization` header, an unknown or revoked API key, or an expired session.
## Codes
Common `code` values (new ones can appear):
- `invalid_api_key`: the key doesn’t exist or was revoked or rotated
- `invalid_session`: the Shopify app’s session token isn’t valid
## What to do
Check that the key is sent as `Authorization: Bearer pf_…` and still listed under Developers → API keys. Don’t retry without new credentials.
Source: https://postforge.dev/developers/problems/unauthenticated
---
# validation
400 The request isn’t valid: what the validation problem from the Postforge API means and what to do.
| Type | Status | Title |
| --- | --- | --- |
| `https://postforge.dev/developers/problems/validation` | 400 | The request isn’t valid |
The request can’t be run as sent: a missing or invalid parameter, an unknown filter value, a body that doesn’t match the operation’s schema, or a missing header.
## Extra fields
- `errors`: One entry per invalid field, with its `path` (`body.state`, `query.pageSize`) and a `message`.
## Codes
Common `code` values (new ones can appear):
- `no_workspace`: a dashboard session request without `X-Postforge-Workspace`
- `idempotency_key_invalid`: an `Idempotency-Key` outside 1 to 255 characters
- `idempotency_key_not_supported`: the operation can’t replay its answer, so it refuses the header
## What to do
Fix the request using `errors` and send it again. Retrying unchanged gets the same answer.
Source: https://postforge.dev/developers/problems/validation
---
# Quickstart
Create a workspace API key and make your first Postforge API request.
## 1. Create an API key
In the Postforge dashboard, open your workspace and go to **Developers → API keys**. Owners and
admins create keys; pick the scope your integration needs (`read` is enough to start). The key is
shown once: copy it and store it as a secret.
## 2. Call the API
Every request carries the key as a bearer token. The key belongs to one workspace, so you never
name the workspace yourself.
```bash tab="cURL"
curl https://api.postforge.dev/v1/orders?pageSize=10 \
-H "Authorization: Bearer $POSTFORGE_API_KEY"
```
```ts tab="TypeScript"
const response = await fetch('https://api.postforge.dev/v1/orders?pageSize=10', {
headers: { Authorization: `Bearer ${process.env.POSTFORGE_API_KEY}` },
})
const orders = await response.json()
```
Lists answer one page at a time:
```json
{
"items": [
{
"id": "0192f3a4-5b6c-7d8e-9f01-23456789abcd",
"number": "#1001",
"placedAt": "2026-10-14T13:05:22.000Z",
"financialStatus": "paid",
"total": { "amount": 4950, "currency": "EUR" },
"locale": "nl-NL"
}
],
"total": 128,
"page": 1,
"pageSize": 10
}
```
## 3. Write safely
Writes need a `write` key. Send an `Idempotency-Key` so a retry after a timeout can never apply the
same change twice. This unsubscribes a profile's email address, with your own form as proof:
```bash tab="cURL"
curl -X PUT https://api.postforge.dev/v1/profiles/$PROFILE_ID/consent/email \
-H "Authorization: Bearer $POSTFORGE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"state": "unsubscribed", "proof": {"reference": "acme.com preference form"}}'
```
```ts tab="TypeScript"
const response = await fetch(
`https://api.postforge.dev/v1/profiles/${profileId}/consent/email`,
{
method: 'PUT',
headers: {
Authorization: `Bearer ${process.env.POSTFORGE_API_KEY}`,
'Content-Type': 'application/json',
// The same key on a retry: the change happens once.
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({ state: 'unsubscribed', proof: { reference: 'acme.com preference form' } }),
},
)
```
## 4. Handle problems
Errors are [problem details](/developers/problems): show `detail` to people, branch on `type`
and `code` in code.
```ts
type Problem = { type: string; title: string; status: number; detail?: string; code?: string }
if (!response.ok) {
const problem: Problem = await response.json()
if (problem.code === 'scope') {
// This key can’t do it: use a key with a larger scope.
}
throw new Error(problem.detail ?? problem.title)
}
```
```json
{
"type": "https://postforge.dev/developers/problems/forbidden",
"title": "You can’t do this",
"status": 403,
"detail": "This needs a key with the write scope.",
"code": "scope"
}
```
## Next
- [Authentication](/developers/authentication): scopes and key rotation
- [Requests](/developers/requests): pagination, filters, money and dates
- [API reference](/developers/api): every operation
Source: https://postforge.dev/developers/quickstart
---
# Rate limits
The Postforge API's rate limits per caller, the RateLimit-Policy and Retry-After headers, and how to back off.
Rate limits keep one runaway client from slowing Postforge down for everyone. Each caller has its
own quota, so one integration hitting its limit never affects another.
| Quota | Limit | Counts |
| --- | --- | --- |
| `standard` | 600 requests a minute | Every request |
| `expensive` | 60 requests a minute | Operations that start heavy work: imports, reading a feed, applying a bulk change |
A caller is an API key, a person signed in to the dashboard, or the Shopify app's store. Limits
never count by IP address. Operations anyone may call without signing in aren't counted.
## Headers
Every counted answer names its quotas in `RateLimit-Policy`
([draft-ietf-httpapi-ratelimit-headers-11](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/)):
```http
RateLimit-Policy: "standard";q=600;w=60
```
An expensive operation lists both: `"standard";q=600;w=60, "expensive";q=60;w=60`.
## When you hit a limit
You get `429` with a [`rate-limited` problem](/developers/problems/rate-limited):
```http
HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 60
RateLimit: "standard";r=0;t=60
{"type":"https://postforge.dev/developers/problems/rate-limited","title":"Too many requests","status":429,"code":"standard_rate_limit","detail":"Too many requests: wait 60 seconds before trying again."}
```
```ts
async function withRetry(request: () => Promise): Promise {
for (;;) {
const response = await request()
if (response.status !== 429) return response
const seconds = Number(response.headers.get('Retry-After') ?? 60)
// Wait as long as asked, plus up to 5 seconds of jitter.
await new Promise((resolve) => setTimeout(resolve, (seconds + Math.random() * 5) * 1000))
}
}
```
Wait at least `Retry-After` seconds, add a little random jitter so many clients don't come back at
the same moment, and spread bulk work out instead of sending it in bursts. Limits are counted
per Cloudflare location and are approximate: treat them as a ceiling, not a budget to use up.
Limits may change as Postforge grows; the `RateLimit-Policy` header always tells you the current
ones.
Source: https://postforge.dev/developers/rate-limits
---
# Requests
Postforge API conventions for URLs, JSON bodies, money, dates, ids, pagination, search, filters and sorting.
## URLs and resources
The API lives at `https://api.postforge.dev/v1`. Resources are plural nouns
(`/v1/orders`, `/v1/orders/{id}`); actions are verbs below a resource with `POST`
(`/v1/product-priority-changes/{id}/apply`). An operation a resource doesn't support doesn't exist.
## Bodies
- JSON in and out, property names in camelCase.
- **Money** is an object in minor units with its ISO 4217 currency: `{ "amount": 4950, "currency": "EUR" }` is €49.50.
- **Timestamps** are ISO 8601 in UTC: `2026-10-16T09:30:00.000Z`.
- **Ids** are UUIDv7 strings. An id that doesn't exist, or belongs to another workspace, answers `404`.
- **Locales** are full BCP 47 locales, a language and a country: `nl-NL`, `en-US`, never `en`.
## Lists
Every list takes the same query parameters:
| Parameter | Meaning |
| --- | --- |
| `page` | Page number, starting at 1 |
| `pageSize` | 10, 25 (default), 50 or 100 |
| `q` | Search text |
| `sort` | A sort key, `-` in front for descending: `sort=-placedAt` |
Filters are named per list and take comma-separated values: `payment=paid,refunded`. An unknown
value is a [validation problem](/developers/problems/validation), never silently ignored.
The answer is a page:
```json
{ "items": [], "total": 128, "page": 2, "pageSize": 25, "all": 412 }
```
`total` counts what matches the search and filters; top-level lists also give `all`, the count
before them. A record's small sub-collections (a profile's merges) answer `{ "items": [] }`.
Large exports and imports are jobs you start and then read, not pages.
## Writes
Send `Content-Type: application/json` and an [`Idempotency-Key`](/developers/idempotency).
Creates answer `201` with the record; changes answer `200`; deletes answer `204`.
Source: https://postforge.dev/developers/requests
---
# Versioning and deprecation
How the Postforge API is versioned, what counts as a breaking change, and how much notice you get before one.
The API's version is in its path: `/v1`. Within a version, Postforge only makes changes that don't
break working integrations; anything that would break one is announced first.
Postforge is in development. Until version 1 of the product, the API can change without notice.
The promises below start at launch.
## What may change without notice
- New operations, resources and webhooks
- New optional request parameters and fields
- New fields in answers, and new values in lists of codes (`code` in a problem, a status)
- New problem `code`s within an existing `type`
Write clients that ignore fields they don't know.
## Breaking changes
Removing or renaming an operation, field or parameter, making an optional parameter required, or
changing what a field means is a breaking change. After launch:
| Change | Notice |
| --- | --- |
| Removing or breaking one operation within `/v1` | **6 months** |
| A new major version (`/v2`) | `/v1` keeps working for **12 months** next to it |
## How deprecation is signalled
A deprecated operation is marked `deprecated` in the [OpenAPI spec](https://api.postforge.dev/v1/openapi.json),
and every answer from it carries:
```http
Deprecation: @1798761600
Sunset: Thu, 01 Jul 2027 00:00:00 GMT
Link: ; rel="deprecation", ; rel="sunset"
```
- `Deprecation` ([RFC 9745](https://www.rfc-editor.org/rfc/rfc9745)): since when, as a Unix timestamp
- `Sunset` ([RFC 8594](https://www.rfc-editor.org/rfc/rfc8594)): when it stops working
- `Link`: the notice explaining what to use instead
Watch for these headers in your logs, and plan the move before the `Sunset` date.
Source: https://postforge.dev/developers/versioning
---
# Get the signed-in user
The user and the workspaces they can open.
## GET /v1/me
API keys: not accepted; a person signed in to the dashboard only.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/me/get
---
# Forget trusted browsers
Every browser asks for a two-factor code again at its next sign-in.
## POST /v1/me/two-factor/trusted-browsers/forget
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/me/forget-trusted-browsers
---
# Turn off email codes
Sign-ins then need the authenticator app or a backup code. Only while the app is on; otherwise turn two-factor off.
## POST /v1/me/two-factor/email/disable
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/me/disable-two-factor-email
---
# List your sessions
Where you’re signed in, most recently active first.
## GET /v1/sessions
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/sessions/list
---
# Sign out a session
That browser or device is signed out at once.
## DELETE /v1/sessions/{id}
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/sessions/delete
---
# Sign out everywhere else
Every session except the one making this request is signed out at once.
## POST /v1/sessions/sign-out-others
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/sessions/delete-others
---
# Create a workspace
The caller becomes its owner; its address (slug) is derived from the name.
## POST /v1/workspaces
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/workspaces/create
---
# Update a workspace
Owner only: its name, and whether members need two-factor on to open it (the owner needs it on first). The address (slug) never changes, so shared links keep working.
## PATCH /v1/workspaces/{id}
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/workspaces/update
---
# Preview a workspace address
The slug a new workspace with this name would get.
## GET /v1/workspaces/available-slug
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `name` | query | yes | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/workspaces/available-slug
---
# List members
People with access to the workspace.
## GET /v1/members
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
| `q` | query | no | Search |
| `role` | query | no | Comma-separated, any of: owner, admin, member |
| `sort` | query | no | One of name, joinedAt; prefix with - for descending |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/members/list
---
# Transfer ownership
Makes this member the owner; the current owner becomes an admin.
## POST /v1/members/{id}/transfer-ownership
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/members/transfer-ownership
---
# Change a member’s role
Admin or member; the owner’s role changes only through a transfer.
## PATCH /v1/members/{id}
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/members/update-role
---
# Remove a member
They lose access to the workspace. The owner can’t be removed.
## DELETE /v1/members/{id}
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/members/delete
---
# Leave the workspace
The caller stops being a member. The owner transfers ownership first.
## POST /v1/members/leave
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/members/leave
---
# List invitations
Open invitations to the workspace, including expired ones.
## GET /v1/invitations
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
| `q` | query | no | Search |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/invitations/list
---
# Invite someone
Emails a link to join the workspace; valid for 7 days. Each person sends at most 50 invitation emails a day (429 `invitation_send_limit`).
## POST /v1/invitations
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/invitations/create
---
# Preview an invitation
For the accept page, before signing in: the id in the emailed link is the capability.
## GET /v1/invitations/{id}/preview
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/invitations/preview
---
# Resend an invitation
Emails it again and makes it valid for another 7 days; not within an hour of the last send (429 `invitation_recently_sent`), and within the daily budget (429 `invitation_send_limit`).
## POST /v1/invitations/{id}/resend
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/invitations/resend
---
# Cancel an invitation
The link stops working.
## DELETE /v1/invitations/{id}
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/invitations/delete
---
# Accept an invitation
For the invited person, signed in with the invited (verified) email address.
## POST /v1/invitations/{id}/accept
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/invitations/accept
---
# Decline an invitation
For the invited person, signed in with the invited email address.
## POST /v1/invitations/{id}/decline
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/invitations/decline
---
# List API keys
The workspace’s keys, active first, newest first; never the keys themselves.
## GET /v1/api-keys
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/apikeys/list
---
# Create an API key
Answers the key once; only its hash is stored.
## POST /v1/api-keys
API keys: not accepted; a person signed in to the dashboard only.
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/apikeys/create
---
# Rotate an API key
Replaces the key with a new one, same name and scope; the old key stops working at once. Answers the new key once.
## POST /v1/api-keys/{id}/rotate
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/apikeys/rotate
---
# Revoke an API key
The key stops working at once; it stays listed as revoked.
## POST /v1/api-keys/{id}/revoke
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/apikeys/revoke
---
# List locales
The default first, then by code.
## GET /v1/locales
API keys: the `read` scope or more.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/locales/list
---
# Add a locale
The workspace’s first locale becomes its default. 409 `locale_exists` when it’s there already.
## POST /v1/locales
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/locales/create
---
# Make a locale the default
The workspace's main locale, used when no locale is given.
## POST /v1/locales/{id}/default
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/locales/set-default
---
# Remove a locale
Only locales people added, and never the default (422 `locale_default`); a store’s locales come back when it syncs (422 `locale_from_store`).
## DELETE /v1/locales/{id}
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/locales/delete
---
# List stores
The workspace’s connected stores, by name.
## GET /v1/stores
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/stores/list
---
# Get a store
The store with its import progress.
## GET /v1/stores/{id}
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/stores/get
---
# List where a store sells
The store’s locales (ARCHITECTURE.md §5.5): every country of its markets in each language its storefront offers there, the primary locale first, then by name. Search matches the code, language, country or currency.
## GET /v1/stores/{id}/locales
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
| `q` | query | no | Search |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/stores/list-locales
---
# Disconnect a store
Uninstalls Postforge from the store on its platform and stops syncing. The store and its history stay in the workspace, marked disconnected; it can be connected again.
## POST /v1/stores/{id}/disconnect
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/stores/disconnect
---
# List a store’s imports
Newest first.
## GET /v1/stores/{id}/imports
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/storeimports/list
---
# Import a store’s history again
Reads every customer and order, or every product, from the store again; live sync continues meanwhile. Records already in Postforge are updated, never duplicated, and only by data at least as new. Imports run one at a time per store: this one may wait in the queue. 409 `import_active` when this kind is queued or running already.
## POST /v1/stores/{id}/imports
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/storeimports/create
---
# Ask to connect a store
Records that this workspace wants the store and answers the platform’s install page. The store’s staff confirm it in the platform before it joins (ARCHITECTURE.md D12).
## POST /v1/connect-intents
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/connectintents/create
---
# Cancel a request to connect a store
Withdraws the workspace’s request before the store’s staff confirm it: the store no longer shows it.
## DELETE /v1/connect-intents/{id}
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/connectintents/delete
---
# List connections
Every store connected to the workspace, and those waiting for confirmation.
## GET /v1/connections
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
| `q` | query | no | Search |
| `type` | query | no | Comma-separated, any of: shopify |
| `state` | query | no | Comma-separated, any of: connected, importing, pending, needs_attention, disconnected |
| `sort` | query | no | One of name, connectedAt; prefix with - for descending |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/connections/list
---
# Preview a connect link
The store’s name and platform, before signing in.
## GET /v1/connect-links/{token}/preview
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `token` | path | yes | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/connectlinks/preview
---
# Get a connect link
The store and the workspaces the signed-in user can add it to.
## GET /v1/connect-links/{token}
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `token` | path | yes | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/connectlinks/get
---
# Complete a connect link
Adds the store to one of the user’s workspaces, or to a new one they own.
## POST /v1/connect-links/{token}/complete
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `token` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/connectlinks/complete
---
# List profiles
The workspace’s people, most recently seen first.
## GET /v1/profiles
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
| `q` | query | no | Search |
| `store` | query | no | Comma-separated ids |
| `customer` | query | no | Only people who did (true) or didn’t (false) place an order |
| `sort` | query | no | One of lastSeenAt, firstSeenAt; prefix with - for descending |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/profiles/list
---
# Get a profile
A merged profile answers with the profile it joined; its id differs from the one asked for.
## GET /v1/profiles/{id}
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/profiles/get
---
# List a profile’s events
The timeline, newest first.
## GET /v1/profiles/{id}/events
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/profiles/list-events
---
# List a profile’s identities
Every identifier the person is known by.
## GET /v1/profiles/{id}/identities
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
| `q` | query | no | Search |
| `type` | query | no | Comma-separated, any of: email, phone, platform_customer_id, shopify_client_id, anonymous_id |
| `sort` | query | no | One of lastSeenAt, firstSeenAt; prefix with - for descending |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/profiles/list-identities
---
# List the profiles merged into a profile
## GET /v1/profiles/{id}/merges
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/profiles/list-merges
---
# List a profile’s customer records
The person as each store knows them.
## GET /v1/profiles/{id}/customers
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/profiles/list-customers
---
# List a profile’s consent changes
Every change of the person’s marketing consent, newest first, with its source and proof. The latest change per channel is the person’s consent.
## GET /v1/profiles/{id}/consent
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
| `channel` | query | no | Comma-separated, any of: email, sms |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/consent/list
---
# Subscribe or unsubscribe a profile’s email
Records the change (source `manual` in the dashboard, `api` with an API key) on the person’s email address and writes it back to every store that knows them. Answers the latest email change; when the person already has this state nothing new is recorded. 409 `no_email` when the profile has no email address. Pass `proof` when the person gave or withdrew consent themselves (e.g. on your own form).
## PUT /v1/profiles/{id}/consent/email
API keys: the `write` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/consent/set-email
---
# List orders
Orders of the workspace’s stores, newest first. A Shopify app session sees its own store only.
## GET /v1/orders
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
| `q` | query | no | Search |
| `store` | query | no | Comma-separated ids |
| `payment` | query | no | Comma-separated, any of: pending, authorized, partially_paid, paid, partially_refunded, refunded, voided |
| `fulfillment` | query | no | Comma-separated, any of: unfulfilled, partially_fulfilled, fulfilled, restocked |
| `profile` | query | no | Only orders of this profile |
| `product` | query | no | Only orders that include this product |
| `placedFrom` | query | no | Placed from (inclusive) |
| `placedTo` | query | no | Placed until (exclusive) |
| `sort` | query | no | One of placedAt, total; prefix with - for descending |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/orders/list
---
# Get an order
The order with its buyer, line items and totals.
## GET /v1/orders/{id}
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/orders/get
---
# List products
Products of the workspace’s stores, by title, in a locale. Search matches the title, vendor, handle or a variant’s SKU. Without a status filter, removed products are left out. A Shopify app session sees its own store only.
## GET /v1/products
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
| `q` | query | no | Search |
| `locale` | query | no | Content locale; the workspace default without one |
| `store` | query | no | Comma-separated ids |
| `status` | query | no | Comma-separated, any of: active, unlisted, draft, archived, removed |
| `stock` | query | no | Comma-separated, any of: in_stock, out_of_stock, not_tracked |
| `sort` | query | no | One of title, price, inventory, changedAt; prefix with - for descending |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/products/list
---
# Get a product
The product’s details in a locale; its variants, sources and sales have their own operations.
## GET /v1/products/{id}
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `locale` | query | no | Content locale; the workspace default without one |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/products/get
---
# List a product’s variants
In the store’s order. Search matches the variant’s title or SKU.
## GET /v1/products/{id}/variants
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
| `q` | query | no | Search |
| `locale` | query | no | Content locale; the workspace default without one |
| `stock` | query | no | Comma-separated, any of: in_stock, out_of_stock, not_tracked |
| `sort` | query | no | One of position, title, price, inventory; prefix with - for descending |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/products/list-variants
---
# Get a product variant
## GET /v1/products/{id}/variants/{variantId}
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `variantId` | path | yes | |
| `locale` | query | no | Content locale; the workspace default without one |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/products/get-variant
---
# List a product’s sources
What each source sent about the product and its variants in a locale, including sources that no longer have it, and which record won each product field.
## GET /v1/products/{id}/sources
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `locale` | query | no | Content locale; the workspace default without one |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/products/list-sources
---
# Sync a product now
Gets the product and its variants from the store’s platform again (changes normally arrive within seconds). A product the platform no longer has comes back `removed` (never deleted). 409 `store_unreachable` when the store must be reconnected; 422 `no_store_source` for a product only feeds have.
## POST /v1/products/{id}/sync
API keys: the `write` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/products/sync
---
# Get a product’s sales
Orders that include the product, units and revenue, placed in the period (all time without one).
## GET /v1/products/{id}/sales
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `from` | query | no | From (inclusive) |
| `to` | query | no | Until (exclusive) |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/products/sales
---
# Get a store’s product priority
The store’s product sources in order (first wins), the fields with an order of their own, the custom fields its sources send, and the change that’s open or was applied last.
## GET /v1/stores/{id}/product-priority
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/productpriority/get
---
# List a store’s priority changes
Newest first: the change that’s open (being previewed, waiting for review or being applied) and the ones applied or failed before. Discarded and replaced changes are gone.
## GET /v1/product-priority-changes
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
| `store` | query | yes | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/productprioritychanges/list
---
# Preview a priority change
Starts working out what every product of the store would show with these rules; nothing changes until it’s applied. Replaces a change that’s still open. 422 `invalid_rules` when the order doesn’t name every source of the store exactly once; 409 `priority_applying` while a change is being applied.
## POST /v1/product-priority-changes
API keys: the `write` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/productprioritychanges/create
---
# Get a priority change
## GET /v1/product-priority-changes/{id}
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/productprioritychanges/get
---
# Discard a priority change
Drops a change that isn’t applied; the priority stays as it is.
## DELETE /v1/product-priority-changes/{id}
API keys: the `write` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/productprioritychanges/discard
---
# List a priority change’s products
The store’s products by title, each with the fields it would show differently. Search matches the title, vendor, handle, a variant’s SKU or the product’s id in a source; `changed=true` lists only products that change.
## GET /v1/product-priority-changes/{id}/products
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
| `q` | query | no | Search |
| `changed` | query | no | `true`: only products that change |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/productprioritychanges/list-products
---
# Apply a priority change
Makes the change the store’s priority and merges every product again with it. 409 `priority_change_not_ready` before its preview is done; 409 `priority_change_stale` when the store’s sources changed since.
## POST /v1/product-priority-changes/{id}/apply
API keys: the `write` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/productprioritychanges/apply
---
# List feeds
The workspace’s product feeds, by name. Search matches the name or URL.
## GET /v1/feeds
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
| `q` | query | no | Search |
| `store` | query | no | Comma-separated ids |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/feeds/list
---
# Add a feed
Adds a feed as a product source of the store, after the store’s other sources, and imports it right away. 422 `feed_mapping_incomplete` when the mapping lacks what the settings need.
## POST /v1/feeds
API keys: the `write` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/feeds/create
---
# Get a feed
## GET /v1/feeds/{id}
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/feeds/get
---
# Change a feed
Changes apply from the next import. 422 `feed_mapping_incomplete` as for create.
## PATCH /v1/feeds/{id}
API keys: the `write` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/feeds/update
---
# Delete a feed
Removes the feed as a source: its data leaves every product, which is merged again from its other sources. Products only the feed had stay, as removed.
## DELETE /v1/feeds/{id}
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/feeds/delete
---
# Preview a feed
Reads the first rows of a feed without adding it: its format, its columns with what each likely is and sample values, and how many rows match a variant or product of the store by id. 422 `feed_unreadable` when the URL can’t be read as a feed.
## POST /v1/feeds/preview
API keys: the `write` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/feeds/preview
---
# Check a feed before adding it
Reads the first rows of a feed with the settings it would get and says how many would import, match nothing (enrich only) or be skipped, with the first skipped rows and why. Adds nothing. 422 `feed_unreadable` as for preview.
## POST /v1/feeds/check
API keys: the `write` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/feeds/check
---
# List a feed’s imports
Newest first.
## GET /v1/feeds/{id}/imports
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/feeds/list-imports
---
# Import a feed now
Imports the feed again now. 409 `import_active` when an import of it is queued or running.
## POST /v1/feeds/{id}/imports
API keys: the `write` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/feeds/create-import
---
# List the rows an import skipped
In file order, with why each was skipped.
## GET /v1/feeds/{id}/imports/{importId}/skipped-rows
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `importId` | path | yes | |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/feeds/list-skipped-rows
---
# List coupons
Newest first. Search matches the name or prefix.
## GET /v1/coupons
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
| `q` | query | no | Search |
| `store` | query | no | Comma-separated ids |
| `status` | query | no | Comma-separated, any of: active, ended, archived |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/coupons/list
---
# Create a coupon
Creates the discount in the store’s platform and starts making codes: a personal coupon keeps some ready as emails go out; a bulk coupon makes `quantity` at once. 422 `invalid_quantity` when a bulk coupon has none (or a personal one has one). 409 `coupons_unsupported` for a store without a supported platform; 409 `store_permission_missing` when the store hasn’t allowed Postforge to manage discounts yet; 409 `store_unreachable` when it must be reconnected; 422 `coupon_refused` with the platform’s reason.
## POST /v1/coupons
API keys: the `write` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/coupons/create
---
# Get a coupon
## GET /v1/coupons/{id}
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/coupons/get
---
# Update a coupon
Changes what its codes give, in the platform too. An imported coupon changes its name, validity and reissue only (what its codes give is set where they came from). A new validity in days applies to codes handed out from now on; a new end date to every code. A bulk coupon’s quantity can grow (422 `invalid_quantity` when it shrinks). The store, kind and prefix stay.
## PATCH /v1/coupons/{id}
API keys: the `write` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/coupons/update
---
# Check a file of codes
Reads a CSV of codes (a column named code, coupon or discount code, else the first column) and says how many would be imported, which the file has twice, which the store has already, and which rows can’t be a code and why. Imports nothing.
## POST /v1/coupons/import-check
API keys: the `write` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/coupons/check-import
---
# Import a coupon’s codes
Creates a coupon from a CSV of codes made elsewhere: Postforge hands them out and sees them used in orders; the discount stays where the codes came from. Codes the store has already are left out. A code works until a date or as long as the coupon is active (422 `invalid_validity` for days). 422 `no_codes` when no row is a code.
## POST /v1/coupons/imports
API keys: the `write` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/coupons/import
---
# Archive a coupon
Turns the discount off in the platform: no code of it works anymore and none is handed out. Its codes stay, as history.
## POST /v1/coupons/{id}/archive
API keys: the `write` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/coupons/archive
---
# List a coupon’s codes
Newest first. Search matches the code or the email it was handed out to.
## GET /v1/coupons/{id}/codes
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
| `q` | query | no | Search |
| `status` | query | no | Comma-separated, any of: pending, available, assigned, used, expired, failed |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/coupons/list-codes
---
# Hand out a code
Hands a code to a profile (its email): the same code never goes to two people. With `after_use_or_expiry` a person whose code still works gets that code again. Its days start when it’s first handed out. 409 `coupon_archived` or `coupon_ended`; 409 `coupon_codes_unavailable` when none is ready yet (more are being made); 422 `profile_without_email`.
## POST /v1/coupons/{id}/codes/assign
API keys: the `write` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/coupons/assign-code
---
# Search the workspace
Profiles (name, email or phone), orders (number, or the buyer’s exact email), products (title, vendor, handle, SKU or id), coupons (name or prefix), feeds (name or URL) and stores (name or domain): the first few of each kind and how many match.
## GET /v1/search
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `q` | query | yes | What to look for |
| `limit` | query | no | Results per kind |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/search/search
---
# List change proposals
Newest first.
## GET /v1/change-proposals
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `page` | query | no | Starts at 1 |
| `pageSize` | query | no | One of 10, 25, 50, 100 |
| `status` | query | no | Comma-separated, any of: open, approved, rejected |
| `kind` | query | no | A definition kind, e.g. template |
| `definitionId` | query | no | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/changeproposals/list
---
# Propose a change
A new version of a definition (`definitionId` and the `baseVersionId` it builds on: the current draft) or a new definition (`name`). The document must fit the kind’s schema (`/v1/schemas/{kind}`) and pass the checks before publish.
## POST /v1/change-proposals
API keys: the `propose` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/changeproposals/create
---
# Get a change proposal
With the proposed document and its diff from the base version (JSON Patch).
## GET /v1/change-proposals/{id}
API keys: the `read` scope or more.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/changeproposals/get
---
# Approve a change proposal
Saves the proposed version and publishes it; a new definition is created as a draft. A stale proposal (409 `proposal_stale`) must be proposed again on the current draft.
## POST /v1/change-proposals/{id}/approve
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/changeproposals/approve
---
# Reject a change proposal
## POST /v1/change-proposals/{id}/reject
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `id` | path | yes | |
| `idempotency-key` | header | no | Run this write once: a retry with the same key gets the first answer (24 hours). |
Takes a JSON body.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/changeproposals/reject
---
# List definition schemas
## GET /v1/schemas
API keys: not accepted; a person signed in to the dashboard only.
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/schemas/list
---
# Get a definition schema
The kind’s document as JSON Schema (draft 2020-12).
## GET /v1/schemas/{kind}
API keys: not accepted; a person signed in to the dashboard only.
| Parameter | In | Required | Description |
| --- | --- | --- | --- |
| `kind` | path | yes | |
Full schemas: the OpenAPI spec at https://api.postforge.dev/v1/openapi.json.
Source: https://postforge.dev/developers/api/schemas/get