Skip to Content
Energon runs in the operator's Cloudflare account. Published links are open by default.
Use EnergonHTTP API

Use this reference to implement an Energon client for discovery, authentication, site and loose-file operations, listing, and error recovery. Query the target instance’s /v1/help for deployment-specific identity and policy, then use the advertised /v1/openapi.json document because it is the canonical HTTP schema. Store returned url and api_url values instead of deriving them from route shapes.

HTTP API

Base URLs and authentication

PUBLIC_ORIGIN/v1/... API; token except help, health, and OpenAPI discovery CONTENT_ORIGIN/{handle}/s/{slug}/... human-facing site content CONTENT_ORIGIN/{handle}/f/{id}/{filename} human-facing loose-file content

The hub origin (PUBLIC_ORIGIN) and content origin (CONTENT_ORIGIN) are different hostnames in production. API responses use:

  • url for the canonical public content URL on CONTENT_ORIGIN.
  • api_url for an authenticated raw-byte API URL on PUBLIC_ORIGIN, when the operation has one.

Do not build the content URL by replacing or appending to the API host. Store the returned value.

Every /v1 route except GET /v1/help, GET or HEAD /v1/health, and GET or HEAD /v1/openapi.json requires:

Authorization: Bearer HUMAN_MINTED_INSTANCE_TOKEN

The token must come from this instance’s /tokens page. Tokens cannot be recovered or renewed after expiry.

Request and storage flow

Sites have a slug plus many paths. Loose files have one generated id and one current filename. The API routes sites by slug and loose files by id; the public content URL additionally includes the publishing handle.

Authenticated site routes are not globally namespaced by handle. For a caller, Energon resolves the caller’s own site with that slug first; otherwise the slug must identify exactly one site on the instance. If two accounts use the same slug, a cross-account api_url can be ambiguous or resolve to the caller’s own site. For a site handoff across accounts, use the returned public path URL (plus X-Energon-Password when protected), or confirm that the slug is unique before relying on the authenticated site URL. Loose-file api_url values do not have this ambiguity because the generated id is instance-wide identity.

Discover the instance

Replace the placeholder host before running this example:

export ENERGON_ORIGIN="https://hub.your-company.example" curl -fsS "$ENERGON_ORIGIN/v1/health" curl -fsS "$ENERGON_ORIGIN/v1/help" | jq '{openapi, content_origin, env, token_prefix, retention, tokens, limits}' curl -fsS "$ENERGON_ORIGIN/v1/openapi.json" | jq '{openapi, info, servers}' curl -fsS "$ENERGON_ORIGIN/llms.txt"

/v1/help describes this deployed instance: origins, token environment and prefix, retention presets, token policy, and limits. Its openapi field names the live OpenAPI document. /v1/openapi.json describes the HTTP contract: paths, request and response schemas, status codes, and error codes. Read both rather than treating either document as a substitute for the other.

Method and pathAuthResponse
GET /v1/healthNo{ "ok": true }
HEAD /v1/healthNoHeader-only health check.
GET /v1/helpNoInstance product/SOP, origins, routes, retention, token policy, identity, and limits.
GET /v1/openapi.jsonNoOpenAPI 3.1 contract with servers[0].url set to this instance’s PUBLIC_ORIGIN.
HEAD /v1/openapi.jsonNoHeader-only availability check for the OpenAPI contract.
GET /llms.txtNoPlain-text agent-oriented description.
GET /v1/whoamiBearer{ email, label, expires_at }; expires_at is null for a never-expiring token.

Common request headers

HeaderRoutesMeaning
Authorization: Bearer …Authenticated /v1 routesUse the token prefix and environment name advertised by /v1/help.
Content-Type: application/jsonSite create/duplicate/PATCH; loose duplicate/PATCHJSON object body.
Content-Type: application/zipSite importZIP bytes for an existing site.
X-FilenameLoose-file POST/PUTRequired for raw create; optional on PUT to rename while replacing.
X-Energon-TTLLoose-file create/duplicateContent lifetime; multipart field ttl is also accepted.
X-Energon-Write-PolicyLoose-file create/duplicateowner or instance; multipart field write_policy is also accepted.
X-Energon-Set-PasswordLoose-file create/duplicate/PUTSet or clear the public share password; multipart field password is also accepted.
X-Energon-Duplicate-FromLoose-file POSTSource file id for server-side copy.
X-Energon-PasswordPublic content URL onlyRead a share-password-protected public content URL. It is not used on authenticated API GETs.

For JSON site requests and JSON loose-file duplicates, use the body fields ttl, write_policy, and password. Header-based loose-file duplication uses the X-Energon-* headers only when the request is not parsed as JSON. Valid write_policy inputs are exactly owner and instance.

Site routes

Route summary

Method and pathRequestSuccess
POST /v1/sitesJSON create, overwrite claim, or duplicate201 create/copy; 200 explicit claim
GET /v1/sitesList query{ sites, total, next_cursor }
GET /v1/sites/{slug}NoneSite metadata plus files
PATCH /v1/sites/{slug}JSON metadata patchUpdated site metadata
DELETE /v1/sites/{slug}None{ ok: true, deleted: slug }
PUT /v1/sites/{slug}/files/{path}Raw bytes201 new path; 200 replacement
GET /v1/sites/{slug}/files/{path}NoneRaw bytes
DELETE /v1/sites/{slug}/files/{path}None{ ok: true, deleted: true, path }
POST /v1/sites/{slug}/importZIP bytes{ slug, url, written }
GET /v1/sites/{slug}/exportNoneZIP attachment

The {path} segment may contain slashes. It must be a safe relative path: no leading slash, drive prefix, or .. segment.

Create, claim, or duplicate a site

Create body:

{ "slug": "onboarding-review", "overwrite": false, "password": "optional-share-password", "ttl": "7d", "write_policy": "owner" }
FieldRequiredMeaning
slugYesLowercase letters, numbers, and internal hyphens; 1–63 characters.
overwriteNoDefaults false. If true, claims an existing site without deleting its paths.
passwordNoPublic-link share password. Empty clears it when claiming.
ttlNoInstance-supported duration, positive seconds, or never when allowed.
write_policyNoowner or instance for a new site or copy; otherwise the instance default. An overwrite claim keeps the existing policy.
duplicate_fromCopy onlySource site slug. Requires a new slug; do not combine with overwrite.

New-site 201 response fields:

FieldMeaning
slug, handleStored site identity.
urlCanonical content-origin site root.
createdtrue.
password_protectedWhether the public content URL is gated.
passwordThe new phrase when this request set one; otherwise null. Use password_protected to determine the current gate state. Later reads omit this field.
ttl, expires_atResolved lifetime and timestamp; expires_at can be null.
write_policyResolved owner or instance.

An overwrite claim returns the same shape with created: false and claimed: true. ttl appears only when the claim supplied one; otherwise expires_at remains unchanged. The claim preserves the existing write_policy; use a creator-authorized PATCH to change it. A collision without overwrite returns 409 site_exists with url, handle, last_written_by, updated_at, file_count, password_protected, expires_at, and hint in the error envelope.

A duplicate request uses the create fields plus duplicate_from. Its 201 response adds duplicated: true, duplicated_from, and file_count. The copy has caller ownership and fresh destination TTL/write policy; it does not inherit the source password unless the request supplies a new one.

Write, read, and delete site paths

export ENERGON_ORIGIN="https://hub.your-company.example" test -n "$ENERGON_TOKEN" || { echo "Set a human-minted ENERGON_TOKEN" >&2; exit 1; } curl -fsS "$ENERGON_ORIGIN/v1/sites/onboarding-review/files/assets/app.css" \ -X PUT \ -H "Authorization: Bearer $ENERGON_TOKEN" \ -H "Content-Type: text/css" \ --data-binary @dist/assets/app.css

Site-file PUT returns { url, api_url, path, size, content_type }. It does not create a missing site and does not extend expiry. GET returns raw bytes with the stored content type. DELETE removes only that path and returns { ok, deleted, path }.

Read site detail

GET /v1/sites/{slug} returns:

  • Site fields: slug, handle, url, created_at, updated_at, created_by, last_written_by, password_protected, expires_at, write_policy.
  • files: ordered path objects with handle, slug, path, size, content_type, updated_at, last_written_by, url, and api_url.

Patch site metadata

{ "password": "", "ttl": "30d", "write_policy": "instance" }

Every field is optional, but write_policy must be present with owner or instance to change it. Empty password clears the public gate. Supplying ttl resets expiry from now and can revive an expired catalog row before purge wins. Response fields are slug, handle, url, password_protected, password, expires_at, write_policy, and optional ttl. password echoes a newly set phrase; it is null when the request cleared or did not change the password, so use password_protected for the current state.

Import and export

Import requires an existing writable site. It replaces archive paths but preserves site paths absent from the archive. One wrapping directory is stripped; common OS junk is skipped. The response’s written array lists the imported paths.

Export requires a non-empty site and returns application/zip with an attachment filename based on the slug. Import, export, and site copy accept at most 200 paths. Import and export are also bounded by the instance file/ZIP byte limit; a site copy is bounded by remaining platform capacity instead.

Public site behavior

The returned root url ends in /. A public root serves index.html, otherwise index.md, otherwise a generated file listing. A specific .md path is rendered for Accept: text/html; ?raw=1 returns source and ?download=1 returns an attachment. Other stored types are served inline unless download is requested.

Loose-file routes

Route summary

Method and pathRequestSuccess
POST /v1/filesRaw upload, multipart upload, or duplicate201 metadata
GET /v1/filesList query{ files, total, next_cursor }
GET /v1/files/{id}Optional ?download=1Raw bytes
PUT /v1/files/{id}Raw or multipart bytes; optional rename200 replacement metadata
PATCH /v1/files/{id}JSON metadata patchUpdated file metadata
DELETE /v1/files/{id}None{ ok: true, deleted: id }

The router also accepts an extra filename segment on authenticated GET|PUT|PATCH|DELETE /v1/files/{id}/{filename}, but identity and lookup use the id.

Create a loose file

Raw upload:

curl -fsS "$ENERGON_ORIGIN/v1/files" \ -H "Authorization: Bearer $ENERGON_TOKEN" \ -H "X-Filename: architecture.pdf" \ -H "X-Energon-TTL: 30d" \ -H "X-Energon-Write-Policy: owner" \ -H "Content-Type: application/pdf" \ --data-binary @architecture.pdf

Accepted request forms:

FormInputs
Raw bytesX-Filename required; optional TTL, write-policy, and set-password headers.
Multipartfile required; optional password, ttl, and write_policy fields.
JSON duplicate{ duplicate_from, filename?, password?, ttl?, write_policy? }.
Header duplicateX-Energon-Duplicate-From; optional X-Filename and policy headers; do not send Content-Type: application/json when using header policy.
Multipart duplicateduplicate_from; optional filename, password, ttl, and write_policy; do not also include file.

If Content-Type is application/json and X-Filename is present without a duplicate header, the JSON text is uploaded as literal file bytes.

Create 201 fields are url, api_url, id, handle, filename, size, content_type, password_protected, password, ttl, expires_at, and write_policy. password contains the newly set phrase or null when no phrase was set.

A duplicate response adds created_by, duplicated: true, and duplicated_from; it otherwise has the create fields. The copy receives a new id and does not inherit source password, TTL, ownership, or write policy.

Replace or rename a loose file

PUT replaces bytes under the existing id. X-Filename is optional: omit it to keep the filename or supply it to rename while replacing. Multipart PUT accepts a file field and uses that file’s name. X-Energon-Set-Password can set or clear the share password during the replacement.

PUT response fields are url, api_url, id, handle, filename, size, content_type, replaced: true, password_protected, and password. password echoes a newly set phrase; it is null when the request cleared or did not change the password. PUT does not return or change TTL/write policy. PATCH does not rename.

Read a loose file

GET /v1/files/{id} returns raw bytes. ?download=1 changes Content-Disposition to attachment. Response headers include Content-Type, Content-Disposition, X-Content-Type-Options: nosniff, X-Energon-Write-Policy, and Content-Length when available.

The public content URL in url includes the current filename. If a client uses the same id with a stale filename, Energon redirects to the current canonical url. Public Markdown follows the same HTML-versus-raw and download rules as site Markdown.

Patch loose-file metadata

PATCH accepts the same JSON fields as site PATCH: optional password, ttl, and write_policy. It returns id, handle, filename, url, api_url, password_protected, password, expires_at, write_policy, and optional ttl. password echoes a newly set phrase; it is null when the request cleared or did not change the password.

Fresh concurrent write claims produce 409 file_busy. Stale claims can be reclaimed. PUT and DELETE conditionally commit against the claim so competing operations do not silently split catalog metadata from stored bytes.

List filters and pagination

GET /v1/sites and GET /v1/files share these query parameters:

ParameterValues and defaultMeaning
scopeinvolved default, created, editedCreated by you or last written by you; created only; or other people’s objects you last edited.
qemptyCase-normalized substring of site slug or loose filename; %, _, and backslash are removed; at most 64 characters.
created_byemptyLowercased exact creator-email filter, combined with scope.
sortupdated default, nameDescending last-update order or ascending slug/filename order.
limit25; clamped to 1–50Page size. Invalid or missing values use 25.
cursoremptyOpaque keyset cursor returned as next_cursor. Invalid cursors restart from the first page.
sites_cursoremptySite-specific cursor accepted in place of cursor.
files_cursoremptyFile-specific cursor accepted in place of cursor.

Do not parse or edit a cursor. Repeat the same filters and pass it back as cursor.

Site list items contain slug, handle, url, created_at, updated_at, created_by, last_written_by, file_count, size, password_protected, expires_at, and write_policy.

Loose-file list items contain id, handle, filename, url, api_url, size, content_type, created_at, created_by, updated_at, last_written_by, password_protected, expires_at, and write_policy.

Lists are scoped to the caller’s involvement view. A valid token can still fetch a known object through its API route even when that object is absent from the list.

Response and error conventions

Successful JSON responses use Content-Type: application/json; charset=utf-8. Binary GET/export responses stream bytes with the stored or generated content type.

Expected /v1 failures use this envelope:

{ "error": "machine_readable_code", "message": "Human-readable recovery guidance.", "hub": "https://hub.your-company.example/account" }

Some failures add fields such as collision metadata, accepted presets, configured limits, an expiry timestamp, or a token page. Consumers should branch on HTTP status and error, show message, and preserve unknown extra fields.

StatusCommon codesClient action
400bad_json, bad_slug, bad_path, bad_filename, bad_password, bad_ttl, ttl_required, ttl_too_long, bad_write_policy, missing_filename, missing_file, bad_duplicate, invalid_zip, bad_zip_path, empty_zip, empty_site, too_many_filesCorrect the request; use advertised presets and limits.
401unauthorized, token_expiredStop for a missing or expired token; a human must mint a replacement.
403forbidden_write, forbidden_write_policy, forbidden_domainUse an authorized creator account or follow instance identity policy.
404not_found, site_not_found, file_not_foundCheck identity/path. Create a site before PUT; loose-file PUT never creates.
409site_exists, site_busy, file_busy, file_write_lost, file_delete_lostResolve a slug collision or retry a transient competing mutation.
410expiredThe content lifetime passed; do not retry the same read as if it were transient.
413too_large, storage_capShrink/split the upload or free instance capacity. Read /v1/help for current limits.
415bad_content_typeSend a JSON object to JSON-only site/PATCH routes.
500storage_rollback_failed, site_import_rollback_failed, site_copy_rollback_failed, site_delete_rollback_failed, file_delete_rollback_failedReport the explicit recovery failure; retry only after storage recovers.
503content_origin_not_configuredThe operator must configure a distinct valid content origin.

An export or server-side copy can also return 500 export_failed or 500 copy_failed when catalog metadata points to missing source bytes. Re-upload the missing source path or file before retrying.

The public content surface has its own JSON gate response when an agent sends X-Energon-Password or requests JSON: 401 { error: "password_required", message }. That response does not include the /v1 envelope’s hub field. Repeated failed guesses can instead return 429 { error: "rate_limited", message }; stop guessing and wait for the gate window.

Common mistakes

Never invent or reuse a token from stale chat history, never default to overwrite: true, and never infer url from PUBLIC_ORIGIN. PUT does not create a missing object or extend TTL. A loose-file rename is PUT with X-Filename; PATCH changes only password, TTL, or write policy. DELETE has no recycle bin.

Last updated on