MacroPrimerGet an API key

REST API

Drive your help center from your own systems: sync articles from a CMS, publish from CI, pull your product knowledge into another tool, or queue walkthrough generation from a script. Everything under /v1 is a stable, versioned contract — new fields and endpoints may be added, existing ones do not change meaning.

Authentication

Every request carries a workspace API key as a bearer token. Create one in Settings → API keys. The key is shown once, at creation: we store only a hash of it, so it cannot be recovered later — if it is lost, revoke it and mint another. Only a workspace owner can manage keys.

curl https://api.macroprimer.com/v1/me \
  -H "Authorization: Bearer mp_live_xxxxxxxxxxxxxxxxxxxx"
  • A key addresses exactly one workspace — its own. No parameter selects a tenant.
  • A key can never mint, modify or revoke keys. That requires a signed-in owner.
  • Keys do not open the dashboard or admin surfaces.
  • Keep keys server-side. A key in browser or mobile code is public. Use one key per system, named for that system, so revoking one never breaks the others.

Your first request

GET /v1/me confirms the key works, names the workspace it addresses, and lists the scopes it holds. It needs no scope — so it is both the right first call and the fastest way to diagnose an unexpected 403.

{
  "api_key": {
    "name": "CI publishing",
    "prefix": "mp_live_a1b2c3d4",
    "scopes": ["articles:read", "articles:write"],
    "expires_at": "2026-11-17T00:00:00+00:00",
    "request_count": 1043
  },
  "workspace": { "id": "c1…", "name": "Acme", "slug": "acme" }
}

Permissions

Scopes are resource:action. Read and write are independent — articles:write does not grant articles:read. Grant exactly what an integration needs. Wildcards (articles:*, *) are accepted but widen silently as new actions ship, so prefer explicit scopes.

articles:readRead articles, bodies and revision history.
articles:writeCreate, update, publish, delete and restore articles.
categories:readRead the collection tree.
categories:writeCreate, update, reorder and delete collections.
comments:readRead editorial comments and findings.
comments:writePost, resolve and delete comments.
search:readSearch the knowledge base.
translations:writeMachine-translate articles and collections.
knowledge:readRead the product map: features, use cases, glossary.
knowledge:writeStart knowledge builds and suggestion runs.
walkthroughs:readList walkthrough jobs and coverage.
walkthroughs:writeEnqueue and retry walkthrough generation.
assets:readList and read assets.
assets:writeUpload and delete assets.
workspace:readRead settings, branding, languages, plan and stats.
workspace:writeUpdate settings, branding and languages.

Conventions

Pagination. List endpoints take limit (1–100, default 25) and offset, and wrap results in a uniform envelope. Page until has_more is false.

{
  "data": [ … ],
  "pagination": { "limit": 25, "offset": 0, "total": 214, "has_more": true }
}

Partial updates. PATCH changes only the fields you send; omitted fields keep their value.

Errors. One envelope everywhere. Branch on error.type, never on the message, which may be reworded.

{ "error": { "type": "forbidden", "status": 403,
             "message": "this API key is missing the required scope: articles:write" } }
401unauthorizedMissing, unknown, revoked or expired key.
402payment_requiredPlan limit reached. X-Upgrade-Plan names the plan that lifts it.
403forbiddenValid key, missing scope. X-Required-Scope names the one needed.
404not_foundNo such resource in this workspace.
409conflictE.g. that language variant already exists.
413payload_too_largeUpload over the size limit.
422invalid_requestValidation failed. Includes a fields array of {field, reason}.
429rate_limitedOver your plan's per-minute budget. Retry-After says how long to wait.
503service_unavailableA prerequisite is missing, e.g. no AI credential configured.

Rate limits. Enforced per workspace — not per key, so extra keys do not buy extra budget — in a fixed 60-second window sized by your plan: 60/min on Free, 300/min on Pro, 1,200/min on Business. Every response reports where you stand, so a client can pace itself rather than discover the limit by being refused.

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 274
X-RateLimit-Reset: 1787506596     # unix seconds, when the window resets

Over the limit returns 429 with Retry-After in seconds. Honour that value instead of guessing a backoff — it is exactly the time left in the window. Refused requests still count, so hammering while limited keeps the window full.

Content model

group_id is the logical document. An article record is one language variant; every translation of it shares a group_id. Sync against group_id, not id. Collections work the same way.

The default language is the source of truth. It defines structure, ordering and the search corpus; other languages overlay text onto it, and anything untranslated falls back — so a locale never shows dead links. Editing the source marks its translations stale, which is your signal to re-translate.

Endpoints

58 operations. The scope each one requires is listed beside it.

Meta

GET/v1/meany key
GET/v1/scopesany key

Articles

List filters: language, collection_id, status, group_id, q, updated_since, include_body.

GET/v1/articlesarticles:read
POST/v1/articlesarticles:write
GET/v1/articles/{id}articles:read
PATCH/v1/articles/{id}articles:write
DELETE/v1/articles/{id}articles:write
POST/v1/articles/{id}/publisharticles:write
POST/v1/articles/{id}/unpublisharticles:write
GET/v1/articles/{id}/revisionsarticles:read
GET/v1/articles/{id}/revisions/{rev_id}articles:read
POST/v1/articles/{id}/revisions/{rev_id}/restorearticles:write

Collections

GET/v1/collectionscategories:read
GET/v1/collections/treecategories:read
POST/v1/collectionscategories:write
GET/v1/collections/{id}categories:read
PATCH/v1/collections/{id}categories:write
POST/v1/collections/reordercategories:write
DELETE/v1/collections/{id}categories:write

Comments

Comments attach to an article's group_id, so they are shared across translations.

GET/v1/articles/{group_id}/commentscomments:read
POST/v1/articles/{group_id}/commentscomments:write
PATCH/v1/comments/{id}comments:write
DELETE/v1/comments/{id}comments:write

Search

GET/v1/searchsearch:read

Translations

Synchronous — one model call per target language. Requires an AI credential on the workspace.

GET/v1/translations/statusworkspace:read
POST/v1/articles/{id}/translatetranslations:write
POST/v1/collections/{id}/translatetranslations:write

Knowledge

Jobs are asynchronous: start, then poll. Starting a kind already running returns the running job.

GET/v1/knowledge/mapknowledge:read
GET/v1/knowledge/statusknowledge:read
GET/v1/knowledge/jobsknowledge:read
POST/v1/knowledge/jobsknowledge:write
GET/v1/knowledge/jobs/{id}knowledge:read
POST/v1/knowledge/jobs/{id}/cancelknowledge:write
GET/v1/knowledge/suggestionsknowledge:read
GET/v1/knowledge/suggestions/{id}knowledge:read
POST/v1/knowledge/suggestions/{id}/dismissknowledge:write

Walkthroughs

Metered against your plan's monthly quota. Over-quota is reported in the response, not raised as an error.

GET/v1/walkthroughswalkthroughs:read
GET/v1/walkthroughs/coveragewalkthroughs:read
GET/v1/walkthroughs/{id}walkthroughs:read
POST/v1/walkthroughswalkthroughs:write
POST/v1/walkthroughs/retrywalkthroughs:write

Assets

Upload sends raw bytes with the format in Content-Type (PNG, JPEG, SVG, WEBP, GIF; max 5 MB).

GET/v1/assetsassets:read
GET/v1/assets/{id}assets:read
POST/v1/assetsassets:write
DELETE/v1/assets/{id}assets:write

Workspace

GET/v1/workspaceworkspace:read
PATCH/v1/workspaceworkspace:write
GET/v1/workspace/brandingworkspace:read
PATCH/v1/workspace/brandingworkspace:write
GET/v1/workspace/languagesworkspace:read
PATCH/v1/workspace/languagesworkspace:write
GET/v1/workspace/statsworkspace:read

Recipes

Publish from CI.

curl -X POST https://api.macroprimer.com/v1/articles \
  -H "Authorization: Bearer $MACROPRIMER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Release 4.2","body":"<p>…</p>","status":"published"}'

Incremental sync. Poll updated_since with the timestamp of your last successful run instead of re-listing everything.

curl -sG https://api.macroprimer.com/v1/articles \
  -H "Authorization: Bearer $MACROPRIMER_KEY" \
  --data-urlencode "updated_since=2026-08-01T00:00:00Z" \
  --data-urlencode "include_body=true" \
  --data-urlencode "limit=100"

Re-translate what has gone stale.

curl -sG https://api.macroprimer.com/v1/articles -H "Authorization: Bearer $KEY" \
  --data-urlencode "limit=100" \
| jq -r '.data[] | select(.translation.stale) | .id' \
| while read id; do
    curl -sX POST "https://api.macroprimer.com/v1/articles/$id/translate" \
      -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
      -d '{"target_langs":["es","fr"]}'
  done