Skip to content

Developers

The Armage API

Your organization's stories, its approval queue and a feed of what was published, for your CMS, intranet or app. Read-only, versioned, with keys you scope and revoke.

Base URL

api.arm-age.com/v1

Auth

Bearer API key

Access

Read-only

Reference

OpenAPI 3.1 · 1.3.0

01

What works today

Live means deployed and usable now. Coming means built or planned, and not usable yet. We change a label when it is true in production, not when the code is written.

Stories for your organization arrive with your newsroom. Until Armage's newsroom is running for your organization, its stories, approval queue and feeds are empty lists: the API answers, there is nothing in it yet. Once it runs, each draft, approval and published story appears here as it happens, and a story publishes only after a person approves it.

API status
CapabilityStatusWhat it is
API keysLiveScoped, expiring, revocable; created by an owner or admin in the Armage workspace.
Stories and approval queue (read)LiveGET /v1/stories, /v1/stories/{id}, /v1/approvals.
JSON Feed and RSS for your CMSLiveGET /v1/feed.json and /v1/feed.xml, the 50 latest published stories.
OpenAPI 3.1 documentLiveGET /v1/openapi.json, public.
Sandbox keysLiveOn request: we open a sandbox workspace with synthetic data and send you its armk_test_ key and address.
WebhooksComingSigned POSTs when a story is submitted, approved, sent back, published or taken down; delivery log over the API.
Status pageComingNot yet published.
Approvals through the APIComingOnly for a signed-in person (OAuth), never a key; needs an owner decision first.
Armage writing into your WordPress or WebflowComingScoped per client; today your CMS pulls the feed or the API.

02

Authentication

Every request carries an organization API key: Authorization: Bearer armk_live_…. An owner or admin creates keys on the API page of the Armage workspace (app.arm-age.com/api), after a two-step sign-in code from the last 12 hours or single sign-on. The key belongs to the organization, not to the person, so it survives staff changes.

  • Shown once. We store only a SHA-256 hash; nobody at Armage can show it to you again.
  • Scoped. Give a key only what the integration reads. There is no write scope.
  • Expiring. 30, 90, 180 or 365 days; every key expires.
  • Revocable. Revoking is immediate and final. At most 20 live keys per organization; creation and revocation are on your audit trail.
  • Environment-tagged. armk_live_ keys work only against https://api.arm-age.com; armk_test_ keys only against the sandbox, which holds synthetic data. Sandbox keys are on request: we open a sandbox workspace for you and send its key and address.
Scopes
ScopeRoutes
stories:readGET /v1/stories, GET /v1/stories/{id}
approvals:readGET /v1/approvals
feed:readGET /v1/feed.json, GET /v1/feed.xml
webhooks:readGET /v1/webhook_endpoints, GET /v1/webhook_deliveries

Rotating a key: create the new key, deploy it, check on the API page that the old key's "last used" date stops moving, then revoke the old key. Two keys can be live at once, so nothing breaks in between.

Keep keys on a server. A key may appear in a URL only on the feeds, and only a key whose sole scope is feed:read: see Feeds for your CMS.

03

Quickstart

Create a key with stories:read, then:

curl
curl "https://api.arm-age.com/v1/stories?status=published&limit=5" \
  -H "Authorization: Bearer $ARMAGE_API_KEY"
TypeScript (fetch)
// Node 20+, Deno, Bun or a Worker. Keep the key on a server.
const res = await fetch('https://api.arm-age.com/v1/stories?status=published&limit=5', {
  headers: { authorization: `Bearer ${process.env.ARMAGE_API_KEY}` },
});
if (!res.ok) {
  const { error } = await res.json();
  throw new Error(`${error.code}: ${error.message} (request ${error.request_id})`);
}
const page = await res.json(); // { object: 'list', data: Story[], has_more, next_cursor }
for (const story of page.data) console.log(story.slug, story.title);

The API is plain HTTPS and JSON, so any language or tool works; there is nothing to install. Every route, parameter and field is in the API reference and the machine-readable OpenAPI document, which most languages can generate a typed client from.

04

Errors

Every error has the same shape. request_id is also in the X-Request-Id header: quote it when you write to us and we find the exact request.

An error
HTTP/1.1 403 Forbidden
Content-Type: application/json
X-Request-Id: 8c1f…

{
  "error": {
    "type": "permission_error",
    "code": "insufficient_scope",
    "message": "This key lacks the approvals:read scope",
    "request_id": "8c1f…"
  }
}
Error types
typeHTTPWhen
invalid_request400, 404, 422Bad parameters, unknown id; error.fields names each field
authentication_error401Missing, malformed, expired, revoked or wrong-environment key
permission_error403The key lacks the scope the route needs
rate_limited429Over the limit; wait for Retry-After
unavailable503Temporarily unavailable; retry after Retry-After
api_error500Our fault; quote the request_id

05

Rate limits

300 requests a minute per key, and 600 a minute per client IP. Over either, the API answers 429 with Retry-After; every authenticated response states the key's policy.

Over the limit
HTTP/1.1 429 Too Many Requests
Retry-After: 60
RateLimit-Policy: 300;w=60

Please do not poll faster than every five minutes. A CMS should read the feed; anything that must react at once should use webhooks once they are live.

06

Pagination

Lists return { object: "list", data, has_more, next_cursor }, newest change first. limit is 1 to 100 (default 25). Pass next_cursor back as cursor for the next page; it is opaque and stays correct while stories change, so no row is skipped or repeated.

Every page (TypeScript)
const headers = { authorization: `Bearer ${process.env.ARMAGE_API_KEY}` };
let cursor: string | null = null;
do {
  const url = new URL('https://api.arm-age.com/v1/stories');
  url.searchParams.set('limit', '100');
  if (cursor) url.searchParams.set('cursor', cursor);
  const page = await (await fetch(url, { headers })).json();
  for (const story of page.data) console.log(story.id);
  cursor = page.has_more ? page.next_cursor : null;
} while (cursor);

07

Versioning and deprecation

  • The version is in the path: /v1.
  • Additive changes need no new version: new routes, new optional parameters, new scopes, new fields in responses, new event types. Ignore fields you do not know.
  • Breaking changes get /v2, and /v1 keeps working alongside it.
  • Nothing is retired without notice: a retiring route answers with Deprecation and Sunset headers at least six months before it stops, and the change is in the changelog.
  • No patient data: Armage does not process protected health information, and no route accepts it.

Questions, a sandbox workspace or a scope you need: hello@arm-age.com.

Build against a sandbox workspace

Or write