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}
| Segment | Meaning | Example |
|---|---|---|
action | Endpoint group (see Endpoint catalogue) | preview |
owner | GitHub org / user | acme |
repo | GitHub repository | acme-website |
ref | Branch / tag | main |
path | Content path (URL-encoded) | /products/widget |
Authentication
Three auth modes are supported, picked in this order of preference:
- API token (server-to-server) - send a project token in either the
x-auth-token: <token>header or theAuthorization: token <token>header. This is the default for CI and scripts. - 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.
- Anonymous - a small subset of read-only endpoints (e.g.
statusfor public sites) is available without auth.
Obtaining a token
# Interactive browser login; stores the token for aem-cli
aem login
# Or log in 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 resulting token in your secret manager 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
| Group | Verbs | Purpose |
|---|---|---|
/status | GET | Inspect a path's preview / live / source state |
/preview | POST, DELETE | Render to preview / unpublish from preview |
/live | POST, DELETE | Promote preview to live / unpublish from live |
/code | POST | Trigger a code-bus refresh after a force push or branch switch |
/index | POST, GET | Rebuild or query indexes (sitemap, search) |
/cache | POST | Programmatic cache purge at the CDN edge |
/log | GET | Per-resource event log - preview / publish / errors |
/profile | GET | Current user / token info - scopes, expiry |
/sitemap | POST, GET | Regenerate or fetch sitemap.xml |
/snapshot | POST, GET, DELETE | Point-in-time content snapshots for rollback |
/job | GET, DELETE | Inspect or cancel async bulk jobs |
/config | GET, PUT, PATCH | Configuration Service: site config, headers, redirects, 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"
},
"source": {
"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 jobId:
{ "jobId": "abc123", "status": "pending", "self": "/job/acme/acme-website/main/abc123" }
/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 the index status:
curl -H "Authorization: token $EDS_API_KEY" \
https://admin.hlx.page/index/acme/acme-website/main
/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
# Purge by surrogate key (e.g. all pages tagged with a section)
curl -X POST -H "Authorization: token $EDS_API_KEY" \
-H "Content-Type: application/json" \
https://admin.hlx.page/cache/acme/acme-website/main \
-d '{ "keys": ["section:products"] }'
/log - per-resource events
curl -H "Authorization: token $EDS_API_KEY" \
"https://admin.hlx.page/log/acme/acme-website/main/products/widget?limit=20"
{
"events": [
{ "ts": "2026-04-30T11:23:14Z", "action": "preview", "user": "alice@acme.com", "status": 200 },
{ "ts": "2026-04-30T11:22:50Z", "action": "source-modified", "user": "alice@acme.com" }
]
}
Useful for auditing who published what and when.
/profile - check your token
curl -H "Authorization: token $EDS_API_KEY" https://admin.hlx.page/profile
{
"name": "ci-republish",
"email": "ci@acme.com",
"scopes": ["preview", "live", "cache:purge"],
"expiresAt": "2027-01-01T00:00:00Z"
}
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
Useful after bulk content changes. EDS regenerates sitemaps on a schedule, but a manual trigger is sometimes faster.
/snapshot - rollback safety net
Snapshots capture the live state of a path (or path glob) at a moment in time. Restore later if a bad publish ships:
# Take a snapshot before a risky publish
curl -X POST -H "Authorization: token $EDS_API_KEY" \
-H "Content-Type: application/json" \
https://admin.hlx.page/snapshot/acme/acme-website/main \
-d '{ "name": "pre-launch", "paths": ["/products/**"] }'
# List snapshots
curl -H "Authorization: token $EDS_API_KEY" \
https://admin.hlx.page/snapshot/acme/acme-website/main
# Restore
curl -X POST -H "Authorization: token $EDS_API_KEY" \
https://admin.hlx.page/snapshot/acme/acme-website/main/pre-launch/restore
/job - inspect bulk operations
Bulk operations return a jobId. Poll for status:
curl -H "Authorization: token $EDS_API_KEY" \
https://admin.hlx.page/job/acme/acme-website/main/abc123
{
"jobId": "abc123",
"status": "running",
"progress": { "total": 120, "succeeded": 87, "failed": 2 },
"errors": [
{ "path": "/products/legacy", "status": 404, "message": "source not found" }
]
}
status values: pending, running, done, failed, cancelled.
/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,
redirects, 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
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(jobId) {
while (true) {
const res = await fetch(
`https://admin.hlx.page/job/${owner}/${repo}/${ref}/${jobId}`,
{ headers },
);
const job = await res.json();
if (job.status === 'done' || job.status === 'failed') 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.jobId);
console.log('preview:', previewResult.progress);
const liveJob = await startBulk('live', paths);
const liveResult = await pollJob(liveJob.jobId);
console.log('live:', liveResult.progress);
Scheduled republish via cron
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.js
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}`,
{ headers },
);
const s = await res.json();
if (s.source.lastModified > s.live.lastModified) {
drifted.push({
path,
sourceModified: s.source.lastModified,
liveModified: s.live.lastModified,
});
}
}
console.table(drifted);
Bulk purge a section
curl -X POST -H "Authorization: token $EDS_API_KEY" \
-H "Content-Type: application/json" \
https://admin.hlx.page/cache/acme/acme-website/main \
-d '{ "keys": ["section:products"] }'
Surrogate keys are emitted as response headers by the pipeline - inspect Surrogate-Key
on a live response to see what's available.
Errors and rate limits
401- token missing or expired (check/profile)403- token lacks the required scope404- path not found in the source for/preview, or not yet previewed for/live409- conflict (e.g. trying to publish an unpreviewed page)429- rate limited; back off (Retry-Afterheader 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 - a republish bot doesn't need
cache:purgeif it never purges. - Rotate keys quarterly; set
expiresAtrather than relying on revocation. - 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
- Sidekick - the UI front-end for many of these endpoints
- Customizing - site configuration for response headers and CDN
- Architecture - how the API ties into the pipeline
- aem.live: Admin API