Skip to main content

Admin API (helix5)

The helix5 Admin API is the programmable interface to your EDS site. Everything the Sidekick can do - preview, publish, purge, list jobs, manage indexes, fetch logs -- is available as an HTTP endpoint at https://admin.hlx.page/. The API powers CI integrations, scheduled republish jobs, content audits, and bespoke author tooling.

This chapter is a working reference: authentication, endpoint catalogue, webhooks, and the workflows teams build on top.

Base URL​

https://admin.hlx.page/{action}/{owner}/{repo}/{ref}/{path}
SegmentMeaningExample
actionEndpoint group (see Endpoint catalogue)preview
ownerGitHub org / useracme
repoGitHub repositoryacme-website
refBranch / tagmain
pathContent path (URL-encoded)/products/widget

Authentication​

Three auth modes are supported, picked in this order of preference:

  1. API token (server-to-server) - send a project token in either the x-auth-token: <token> header or the Authorization: token <token> header. This is the default for CI and scripts.
  2. Sidekick session cookie - when the request comes from a logged-in author via the Sidekick, the session cookie auths the call. You don't manage this; the extension does.
  3. Anonymous - a small subset of read-only endpoints (e.g. status for public sites) is available without auth.

Obtaining a token​

# Create an Admin API key for automation
curl -X POST https://admin.hlx.page/config/{org}/sites/{site}/apiKeys.json \
-H 'x-auth-token: <admin token>' \
-H 'content-type: application/json' \
--data '{"description":"CI republish","roles":["publish"]}'

# Or log in interactively through the Admin API UI, then inspect your profile
open https://admin.hlx.page/login
open https://admin.hlx.page/profile

For automation, store the returned key value in your secret manager (it cannot be retrieved again) and rotate it regularly. Scope and issuance depend on the site's Admin API / Configuration Service setup.

Using the key​

curl -H "Authorization: token $EDS_API_KEY" \
https://admin.hlx.page/status/acme/acme-website/main/products/widget
curl -H "x-auth-token: $EDS_API_KEY" \
https://admin.hlx.page/status/acme/acme-website/main/products/widget
const res = await fetch(
`https://admin.hlx.page/status/${owner}/${repo}/${ref}${path}`,
{ headers: { Authorization: `token ${process.env.EDS_API_KEY}` } },
);
const status = await res.json();

Endpoint catalogue​

GroupVerbsPurpose
/statusGETInspect a path's preview / live / source state
/previewPOST, DELETERender to preview / unpublish from preview
/livePOST, DELETEPromote preview to live / unpublish from live
/codePOSTTrigger a code-bus refresh after a force push or branch switch
/indexPOST, GETRebuild or query indexes (sitemap, search)
/cachePOSTProgrammatic cache purge at the CDN edge
/logGETProject audit log (preview / publish operations)
/profileGETLogged-in user's profile (name, email, token ttl)
/sitemapPOSTRegenerate sitemap.xml
/snapshotPOST, GETStage previewed pages and publish them together
/jobGET, DELETEInspect or cancel async bulk jobs
/configGET, POST, PUT, DELETEConfiguration Service: site config, headers, access rules

/status - inspect a resource​

curl -H "Authorization: token $EDS_API_KEY" \
https://admin.hlx.page/status/acme/acme-website/main/products/widget
{
"webPath": "/products/widget",
"resourcePath": "/products/widget.md",
"preview": {
"status": 200,
"url": "https://main--acme-website--acme.aem.page/products/widget",
"lastModified": "2026-04-30T11:23:14Z",
"sourceLastModified": "2026-04-30T11:22:50Z"
},
"live": {
"status": 200,
"url": "https://main--acme-website--acme.aem.live/products/widget",
"lastModified": "2026-04-29T17:00:11Z",
"sourceLastModified": "2026-04-29T16:59:48Z"
},
"edit": {
"status": 200,
"lastModified": "2026-04-30T11:22:50Z"
}
}

Read this to detect drift between source, preview, and live - the basis of any republish workflow.

/preview - render to preview​

POST triggers a preview render:

curl -X POST -H "Authorization: token $EDS_API_KEY" \
https://admin.hlx.page/preview/acme/acme-website/main/products/widget

DELETE unpublishes the preview (the URL returns 404 until re-previewed):

curl -X DELETE -H "Authorization: token $EDS_API_KEY" \
https://admin.hlx.page/preview/acme/acme-website/main/products/widget

For bulk operations send paths as the body:

curl -X POST -H "Authorization: token $EDS_API_KEY" \
-H "Content-Type: application/json" \
https://admin.hlx.page/preview/acme/acme-website/main/* \
-d '{ "paths": ["/products/widget", "/products/gadget", "/about/team"] }'

The bulk variant is async and returns a job:

{
"messageId": "12345",
"job": { "topic": "preview", "name": "job-123", "state": "created" },
"links": { "self": "https://admin.hlx.page/job/acme/acme-website/main/preview/job-123" }
}

/live - promote to live​

Same shape as /preview. POST publishes, DELETE unpublishes:

# Publish a single page
curl -X POST -H "Authorization: token $EDS_API_KEY" \
https://admin.hlx.page/live/acme/acme-website/main/products/widget

# Bulk publish from a sitemap-like list
curl -X POST -H "Authorization: token $EDS_API_KEY" \
-H "Content-Type: application/json" \
https://admin.hlx.page/live/acme/acme-website/main/* \
-d '{ "paths": ["/products/widget", "/products/gadget"] }'

/code - refresh code-bus​

After a force-push, branch switch, or large refactor, trigger a code-bus refresh so the pipeline pulls the latest blocks and scripts:

curl -X POST -H "Authorization: token $EDS_API_KEY" \
https://admin.hlx.page/code/acme/acme-website/main/*

Only needed in the unusual case where the repo state is ahead of what EDS has cached (for example, after git push --force). Normal pushes are auto-detected.

/index - rebuild indexes​

Indexes (defined in helix-query.yaml) feed sitemaps, search experiences, and auto-block sources. Force a rebuild:

curl -X POST -H "Authorization: token $EDS_API_KEY" \
https://admin.hlx.page/index/acme/acme-website/main/*

GET returns one resource's index entry:

curl -H "Authorization: token $EDS_API_KEY" \
https://admin.hlx.page/index/acme/acme-website/main/products/widget

/cache - programmatic purge​

Push-invalidation usually handles purges, but you can force one:

# Purge a single path
curl -X POST -H "Authorization: token $EDS_API_KEY" \
https://admin.hlx.page/cache/acme/acme-website/main/products/widget

/log - project audit log​

curl -H "Authorization: token $EDS_API_KEY" \
"https://admin.hlx.page/log/acme/acme-website/main?since=1h"
{
"from": "2026-04-30T10:23:14Z",
"to": "2026-04-30T11:23:14Z",
"entries": [
{ "timestamp": 1777548194000, "route": "preview", "path": "/products/widget", "user": "alice@acme.com", "status": 200 }
]
}

Useful for auditing who published what and when.

/profile - check your token​

curl -H "Authorization: token $EDS_API_KEY" https://admin.hlx.page/profile
{
"profile": {
"iss": "https://auth.hlx.page",
"email": "ci@acme.com",
"name": "ci-republish",
"ttl": 7200
}
}

Check this from CI on startup - a 401 here is a friendlier failure mode than a 401 mid-job.

/sitemap - regenerate sitemap.xml​

curl -X POST -H "Authorization: token $EDS_API_KEY" \
https://admin.hlx.page/sitemap/acme/acme-website/main/sitemap.xml

Useful after bulk content changes. EDS regenerates sitemaps on a schedule, but a manual trigger is sometimes faster.

/snapshot - stage previewed pages​

Snapshots copy the current preview state of pages for review on .aem.reviews and joint publishing (there is no restore):

# Stage pages into a snapshot
curl -X POST -H "Authorization: token $EDS_API_KEY" \
-H "Content-Type: application/json" \
https://admin.hlx.page/snapshot/acme/acme-website/main/pre-launch/* \
-d '{ "paths": ["/products/widget", "/products/gadget"] }'

# List snapshots
curl -H "Authorization: token $EDS_API_KEY" \
https://admin.hlx.page/snapshot/acme/acme-website/main

# Publish all staged resources in the snapshot
curl -X POST -H "Authorization: token $EDS_API_KEY" \
"https://admin.hlx.page/snapshot/acme/acme-website/main/pre-launch?publish=true"

/job - inspect bulk operations​

Bulk operations return a job URL in links.self. Poll for status:

curl -H "Authorization: token $EDS_API_KEY" \
https://admin.hlx.page/job/acme/acme-website/main/preview/job-123
{
"topic": "preview",
"name": "job-123",
"state": "running",
"progress": { "total": 120, "processed": 87, "failed": 2 }
}

state values: created, running, stopped.

/config - Configuration Service​

The Configuration Service uses site-oriented URLs rather than the content action URL shape:

https://admin.hlx.page/config/{org}/sites/{site}.json

Use it to manage site configuration such as content sources, path mappings, headers, access rules, Sidekick configuration, indexes, sitemaps, and robots.txt. Older projects keep parts of this configuration in files (paths.json, helix-query.yaml, helix-sitemap.yaml) or in .helix/* spreadsheets in the content source. Before changing a repository file, verify which settings the Configuration Service owns for the site.

Webhooks​

Webhook support and event names depend on the configured Admin API/Configuration Service capabilities for the site. Verify the available webhook endpoints and event names in the Admin API UI before documenting them as contractually stable for a project.

Each payload includes the path, actor, timestamp, and an HMAC signature you should verify against the secret.

Common workflows​

Republish a sitemap of pages​

scripts/republish-all.mjs
import { setTimeout as wait } from 'node:timers/promises';

const { EDS_API_KEY } = process.env;
const owner = 'acme';
const repo = 'acme-website';
const ref = 'main';
const headers = { Authorization: `token ${EDS_API_KEY}`, 'Content-Type': 'application/json' };

async function startBulk(action, paths) {
const res = await fetch(`https://admin.hlx.page/${action}/${owner}/${repo}/${ref}/*`, {
method: 'POST',
headers,
body: JSON.stringify({ paths }),
});
if (!res.ok) throw new Error(`${action} failed: ${res.status}`);
return res.json();
}

async function pollJob(url) {
while (true) {
const res = await fetch(url, { headers });
const job = await res.json();
if (job.state === 'stopped') return job;
await wait(2000);
}
}

const sitemap = await fetch(`https://www.example.com/sitemap.xml`).then((r) => r.text());
const paths = [...sitemap.matchAll(/<loc>https?:\/\/[^/]+(\/[^<]*)<\/loc>/g)].map((m) => m[1]);

const previewJob = await startBulk('preview', paths);
const previewResult = await pollJob(previewJob.links.self);
console.log('preview:', previewResult.progress);

const liveJob = await startBulk('live', paths);
const liveResult = await pollJob(liveJob.links.self);
console.log('live:', liveResult.progress);

Scheduled republish via cron​

The script uses import and top-level await, so it must run as an ES module: keep the .mjs extension (or add "type": "module" to the project's package.json). A .mjs file is an ES module regardless of the package type; a plain .js file in a project without "type": "module" is CommonJS, which Node 22.7+ reparses as ESM with a MODULE_TYPELESS_PACKAGE_JSON warning and older versions reject.

.github/workflows/scheduled-republish.yml
name: Scheduled republish
on:
schedule:
- cron: '0 4 * * *' # 04:00 UTC daily
workflow_dispatch:

jobs:
republish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22 }
- run: node scripts/republish-all.mjs
env:
EDS_API_KEY: ${{ secrets.EDS_API_KEY }}

Audit drift between source and live​

const drifted = [];
for (const path of paths) {
const res = await fetch(
`https://admin.hlx.page/status/${owner}/${repo}/${ref}${path}?editUrl=auto`,
{ headers },
);
const s = await res.json();
if (s.edit?.lastModified > s.live?.lastModified) {
drifted.push({
path,
sourceModified: s.edit.lastModified,
liveModified: s.live.lastModified,
});
}
}
console.table(drifted);

Errors and rate limits​

  • 401 - token missing or expired (check /profile)
  • 403 - access role lacks the required permission
  • 404 - path not found in the source for /preview, or not yet previewed for /live
  • 409 - conflict with the resource state (e.g. changing a locked snapshot)
  • 429 - rate limited; back off (Retry-After header indicates wait time)
  • 5xx - pipeline error; usually transient, retry with exponential backoff

Bulk operations are idempotent at the path level - re-running a POST /preview on an already-previewed path is safe.

Security checklist​

  • Store API tokens in your secret manager (GitHub Actions secrets, Vault, AWS Secrets Manager). Never commit them.
  • Scope keys narrowly - give each key the narrowest role (e.g. publish).
  • Rotate keys quarterly; create keys with expiresIn and rotate them before their expiration.
  • Verify webhook HMAC signatures before acting on the payload.
  • Restrict outbound webhook URLs to your own domain to limit the blast radius if a config write is compromised.

See also​