MacroPrimer

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.

Full endpoint reference

Every endpoint with its required scope, parameters, request and response schemas, examples and error codes — generated from the API itself, so it always matches what the API returns.

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, items, surfaces, search, export and audit.
knowledge:writeStart knowledge builds and suggestion runs; set the product profile.
knowledge:ingestUpload what the MacroPrimer GitHub Action derives from a repository, in place of repository access.
source:readSee the repository file paths behind knowledge items and surfaces.
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
GET/v1/knowledge/overviewknowledge:read
GET/v1/knowledge/itemsknowledge:read
GET/v1/knowledge/items/{key}knowledge:read
GET/v1/knowledge/surfacesknowledge:read
GET/v1/knowledge/surfaces/{key}knowledge:read
GET/v1/knowledge/searchknowledge:read
GET/v1/knowledge/exportknowledge:read
GET/v1/knowledge/auditknowledge:read
GET/v1/knowledge/profileknowledge:read
PUT/v1/knowledge/profileknowledge:write
DELETE/v1/knowledge/profileknowledge:write
POST/v1/knowledge/action/sessionknowledge:ingest
POST/v1/knowledge/action/tokenknowledge:ingest
PUT/v1/knowledge/action/bundleknowledge:ingest

MCP

The same knowledge as read-only tools for AI agents, over the Model Context Protocol (Streamable HTTP). Tools: get_overview, search_knowledge, get_item, list_items, list_surfaces, get_surface, lookup_term. Repository paths need source:read.

POST/mcpknowledge:read

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