Skip to Content
Use EnergonLet a machine upload without a token

Let a machine that has no API token upload one file to this Energon, with the bytes going straight to the content origin instead of through you. Use an upload grant because it is the narrowest credential that works: it covers one target, works once, and expires within an hour. A fleet orchestrator, a CI job, or a sandboxed agent can then publish a file it built locally without ever holding an account token.

Let a machine upload without a token

When to use a grant

Pick the credential by what the other machine needs to do:

The other machine needs toUse
Upload one file once, now (a new file, a replacement, or one site path)An upload grant (this page)
Keep updating one existing public URL over time, from outside this EnergonA write password
Act as an account on many objects (list, read through /v1, publish freely)Its own API token through Connect an agent

A grant acts as the account that minted it: the upload passes that account’s write policy, and that account becomes the writer. It is not an account, it cannot call /v1, and it cannot be used as an API token.

1. Token holder: mint the grant

You need an API token that can write the target. Choose exactly one target:

  • {"type": "new_file", "filename": "report.pdf"} creates a loose file owned by your account when the upload lands. Optional write_policy works as it does on POST /v1/files. Optional ttl must be one of this Energon’s retention preset ids from GET /v1/help; unlike POST /v1/files, seconds are not accepted.
  • {"type": "file", "id": "<file id>"} replaces an existing loose file at the same URL.
  • {"type": "site_path", "site_id": "<site id>", "path": "index.html"} adds or replaces one path in an existing site. A folder needs one grant per path.
export ENERGON_ORIGIN="https://hub.your-company.example" test -n "$ENERGON_TOKEN" || { echo "Set a human-authorized ENERGON_TOKEN" >&2; exit 1; } GRANT_JSON=$(curl -fsS "$ENERGON_ORIGIN/v1/grants" \ -H "Authorization: Bearer $ENERGON_TOKEN" \ -H "Content-Type: application/json" \ --data '{"target":{"type":"new_file","filename":"report.pdf"},"expires_in":"15m","max_bytes":10485760}') printf '%s' "$GRANT_JSON" | jq '{id, upload_url, expires_at, max_bytes, url, status_url}' GRANT_ID=$(printf '%s' "$GRANT_JSON" | jq -r .id)

Optional fields:

  • expires_in: 5m, 15m (default), 30m, or 1h. A grant never outlives the token that minted it, so expires_at can be earlier than you asked for.
  • max_bytes: the largest body the upload may send, capped at this Energon’s per-file limit.
  • sha256: the expected SHA-256 of the body, as hex. An upload whose bytes do not match publishes nothing.

The response returns secret once, with the no-store header. url is the public URL the upload will change for file and site_path targets, and null for new_file, whose URL comes back from the upload and from the status read.

2. Hand off the upload URL and secret

Send the other machine upload_url and secret, plus max_bytes and sha256 if you set them, so it knows the limits. Use whatever private channel your system already has, such as an orchestrator task payload. The other machine needs nothing else: no token, no Access session, and no /connect.

3. Uploading machine: PUT the bytes

Send the file as the raw body with the secret in the Authorization header. Do not send a filename, password, TTL, or content type header; the grant fixed the target at mint, and metadata headers are 400. The uploader cannot choose the content type: a new file’s type follows its filename and contents, and a replaced file keeps its stored type.

export UPLOAD_URL="https://content.your-company.example/_grants/<grant id>" export GRANT_SECRET="<grant secret>" curl -fsS -X PUT "$UPLOAD_URL" \ -H "Authorization: Bearer $GRANT_SECRET" \ --data-binary @report.pdf

curl --data-binary @file sets Content-Length, which bodies over 25 MB require. Success is 201 for a new file or path and 200 for a replacement, with the public url. The grant is then used up. If the machine is lost, it can read GET /llms.txt on the content origin, which describes this protocol.

4. Token holder: read the result

curl -fsS "$ENERGON_ORIGIN/v1/grants/$GRANT_ID" \ -H "Authorization: Bearer $ENERGON_TOKEN" | jq '{state, url, last_error, expires_at}'

Any token of the minting account can read it. state is one of:

  • unused: not uploaded yet. The same secret still works until expires_at.
  • uploading: an upload holds the grant right now.
  • consumed: published; url is the public URL.
  • failed: ended for good; last_error says why, for example token_revoked or file_not_found. Mint a new grant.
  • expired: the grant ran out before a successful upload. Mint a new grant.

Grant records are deleted a day after they expire, after which the status read is 404 grant_not_found.

Recover from the usual failures

StatuserrorWhat it means
404grant_invalidWrong grant id or secret. Check what was handed off.
410grant_usedAlready used. The body names the published url.
410grant_expiredThe grant ran out. Ask the token holder for a new one.
410grant_failedEnded for good: the minting token was revoked, the target was deleted, or the account lost write access. The body has reason. Ask for a new grant.
409grant_busy / file_busyAnother upload or write is in progress. Retry the same upload.
413too_largeThe body is over max_bytes or this Energon’s limit. Nothing was published.
413storage_capThis Energon is out of storage. Free space, then retry with the same grant.
400checksum_mismatchThe bytes do not match sha256. Nothing was published; retry with the right bytes.
400bad_request / bad_content_typeA metadata header or a multipart body was sent. Send raw bytes only.

Retryable failures (409, 413 storage_cap, and most 5xx) hand the grant back, so the same secret works until it expires. The exception is 500 storage_rollback_failed: storage could not be restored, so the grant stays held until it expires and retries return 409 grant_busy. Report it, and mint a new grant once storage recovers. Grant responses on the content origin never include a hub field or an api_url. A PUT to the upload path on the hub origin returns 404 naming the content-origin URL, because clients drop Authorization on a cross-host redirect.

The grant secret is a credential. Never put it in a URL, a log, a commit, or a published file. Revoking the token that minted a grant ends that grant, and revoking a person’s tokens ends every grant they minted. There is no separate per-grant revoke; grants last at most an hour.

For the exact request and response fields, see HTTP API. For how grants sit next to share passwords, write passwords, and write policy, read Sharing, access, and expiry.

Last updated on