Reference
API and schema
v1 contracts for heartbeats, performance telemetry, logs, events, suite runs, and authenticated fleet reads.
Reviewed 2026-07-24
The API separates ingest from reads. Ingest endpoints use HMAC; read endpoints accept an administrator session or a valid Bearer token.
Endpoints
| Method | Path | Auth | Purpose |
|---|---|---|---|
POST |
/api/ingest |
HMAC | Agent heartbeat. |
POST |
/api/ingest/telemetry |
HMAC | Bounded Monitor performance window. |
POST |
/api/ingest/logs/plans |
HMAC | Reserve one bounded log chunk and obtain an upload grant. |
POST |
/api/ingest/logs/commits |
HMAC | Confirm one uploaded log chunk through file.cheap. |
POST |
/api/ingest/logs/finalize |
HMAC | Finalize one bounded log session after all chunks commit. |
POST |
/api/ingest/events |
HMAC | Batch of explicit events. |
POST |
/api/ingest/runs |
HMAC | Results and artifacts. |
GET |
/api/fleet |
admin | Aggregated fleet view. |
GET |
/api/logs |
admin | Bounded log sessions, chunks, and redacted excerpts. |
POST |
/api/logs/downloads |
admin + Vercel OIDC | Revalidate one committed log chunk and redirect to an exact short-lived file.cheap download. |
POST |
/api/auth/login |
token in body | Creates an HttpOnly cookie. |
POST |
/api/auth/logout |
none | Clears the cookie if present. |
GET |
/api/health |
none | Minimal process liveness. |
GET |
/api/ready |
cron Bearer | Redacted runtime and database readiness. |
GET |
/api/cron/costs |
cron Bearer | Provider inventory reconciliation and estimated cost snapshots. |
GET |
/api/cron/retention |
cron Bearer | Heartbeat pruning, telemetry downsampling, and session finalization. |
Writes are disabled while Chalupa runs in demo mode.
POST /api/logs/downloads accepts JSON or an HTML form with exactly
environmentSlug, chunkId, and artifactId. These values are stable
identifiers, not download capabilities. Chalupa joins the chunk back to its
environment in Neon, requires the stored state to be committed, and verifies
file.cheap's response against the stored artifact reference and SHA-256 before
issuing a 303. Cookie-authenticated forms require exact same-origin browser
provenance. Chalupa also requires the signed target to match the exact immutable
Vercel private Blob path and refuses a grant that outlives artifact retention.
The response is no-store and no-referrer; the signed URL and complete log
bytes are never stored by Chalupa.
Ingest authentication
Required headers:
content-type: application/json
x-chalupa-timestamp: <epoch-seconds>
x-chalupa-nonce: <unique-value>
x-chalupa-signature: v1=<hmac-sha256-hex>
Canonical string:
v1
<METHOD>
<PATH>
<TIMESTAMP>
<NONCE>
<SHA256_HEX_OF_BODY>
The signature uses CHALUPA_INGEST_KEY, which must contain 32 to 1024 bytes
with no leading or trailing whitespace or line breaks. The timestamp allows a
limited window, and each nonce is claimed exactly once.
Heartbeat
{
"version": "1",
"environmentSlug": "demo-harbor",
"environmentName": "Demo Harbor",
"observedAt": "2026-07-23T18:20:00.000Z",
"deployment": {
"provider": "digitalocean",
"providerId": "10001",
"region": "nyc3",
"sizeSlug": "s-2vcpu-4gb",
"launchedAt": "2026-07-23T17:50:00.000Z"
},
"ip": "192.0.2.44",
"uptimeSeconds": 1800,
"containers": [
{
"name": "demo-harbor-api-1",
"service": "api",
"role": "application",
"logs": "include",
"state": "running",
"memoryMiB": 312,
"cpuPercent": 4.8
}
],
"reaper": {
"idleChecks": 2,
"checkIntervalSeconds": 300,
"limitMinutes": 45
},
"disk": {
"mountedGiB": 20,
"usedGiB": 1.4
},
"agentVersion": "1"
}
service, role, logs, databaseKind, primary, and initializes are
optional, allowlisted metadata derived from the validated Compose plan. The
agent never forwards arbitrary labels, image references, environment values,
commands, container IDs, or hostnames. Older agents may send only the
container name and state.
Accepted response:
{
"accepted": true
}
The server can include additional counters or identifiers.
Performance telemetry
The adapter wraps one exact Monitor v1 window with the Chalupa deployment identity. This example is synthetic:
{
"version": "1",
"environmentSlug": "demo-harbor",
"deployment": {
"provider": "digitalocean",
"providerId": "10001"
},
"schema_version": 1,
"kind": "monitor.telemetry_window",
"session_id": "0123456789abcdef0123456789abcdef",
"sequence": 1,
"emitted_at": "2026-07-24T18:20:30.100Z",
"producer": {
"name": "monitor",
"version": "1.14.0"
},
"window": {
"from": "2026-07-24T18:20:00.000Z",
"to": "2026-07-24T18:20:30.000Z",
"sample_interval_ms": 5000,
"sample_count": 6,
"partial": false
},
"metrics": {
"system.cpu.usage": {
"unit": "percent",
"count": 6,
"min": 8.1,
"avg": 15.2,
"p95": 22.4,
"max": 22.4,
"last": 17.1
},
"system.memory.used": {
"unit": "bytes",
"count": 6,
"min": 536870912,
"avg": 541065216,
"p95": 545259520,
"max": 545259520,
"last": 543162368
}
},
"availability": {
"system.cpu.usage": {
"state": "observed",
"observed_samples": 6,
"missing_samples": 0
},
"system.memory.used": {
"state": "observed",
"observed_samples": 6,
"missing_samples": 0
},
"system.memory.available": {
"state": "unavailable",
"observed_samples": 0,
"missing_samples": 6
},
"system.memory.usage": {
"state": "unavailable",
"observed_samples": 0,
"missing_samples": 6
},
"system.memory.pressure": {
"state": "unavailable",
"observed_samples": 0,
"missing_samples": 6
},
"system.swap.used": {
"state": "unsupported",
"observed_samples": 0,
"missing_samples": 6
},
"system.network.receive_rate": {
"state": "unavailable",
"observed_samples": 0,
"missing_samples": 6
},
"system.network.transmit_rate": {
"state": "unavailable",
"observed_samples": 0,
"missing_samples": 6
},
"system.disk.read_rate": {
"state": "unavailable",
"observed_samples": 0,
"missing_samples": 6
},
"system.disk.write_rate": {
"state": "unavailable",
"observed_samples": 0,
"missing_samples": 6
},
"system.load.one_minute": {
"state": "unavailable",
"observed_samples": 0,
"missing_samples": 6
}
},
"alerts": [
{
"rule": "cpu_threshold",
"severity": "warning",
"count": 1
}
]
}
Every availability entry is required even when its metric is not observed. Metrics are optional only when their observed count is zero. The API rejects unknown fields, metrics, units, alert categories, non-finite numbers, invalid summary ordering, impossible counts, future timestamps, and bodies larger than 64 KiB.
session_id plus sequence is the producer retry identity within a
deployment. Repeating identical normalized content deduplicates; changing the
same identity returns telemetry_ingest_conflict. A window whose Monitor
producer version differs from the version pinned for its deployment returns
telemetry_monitor_version_mismatch. The API stores only the validated
projection, never the raw source line.
Source windows and their detailed anomaly rows are retained for 7 days and then atomically folded into indefinite hourly rollups before deletion. Each observed metric carries a bounded internal coverage duration derived from its observed sample count and cadence, capped by the signed source window. Hourly aggregation sums that duration independently of the hour-shaped display bucket, keeping rate integrations honest when data is sparse or partial. Deployment, session, hourly anomaly counts, and retry identity survive compute destruction and downsampling.
The private retention cron drains this work in bounded batches until no
eligible source row remains or its execution budget expires. The response
distinguishes exact processed-row counters from the boolean
telemetryBacklogPending and the drained, deadline, or batch-limit stop
reason. Concurrent cron calls cannot count the same source row twice: the
hourly merge uses a nonblocking advisory lock, a post-lock row recheck, and an
all-or-nothing delete. Terminal summaries read detailed and hourly rows in a
single database snapshot, so an atomic move is counted on exactly one side.
Their final write is optimistic: an accepted delayed window clears the old
summary and changes the session version, causing an older snapshot to become a
no-op until a later run recomputes it.
The production Vercel Pro schedule invokes it at minute 43 of every hour.
Events
{
"version": "1",
"environmentSlug": "demo-harbor",
"deploymentProviderId": "10001",
"deployment": {
"provider": "digitalocean",
"sizeSlug": "s-2vcpu-4gb",
"region": "sfo3",
"dropletHourlyUsd": 0.04167,
"volumeGiB": 10,
"monitorVersion": "1.14.0"
},
"events": [
{
"sourceEventId": "evt_demo_launched_001",
"occurredAt": "2026-07-23T17:50:00.000Z",
"kind": "launched",
"reason": "manual"
}
]
}
deployment is required when the payload includes launched and is rejected
when the payload has no launch event. It captures the bounded provider
metadata and launch-time rate needed to account for a short-lived deployment
before the next inventory poll. The optional Monitor version lets the console
distinguish not configured from configured but waiting. It never carries a
provider credential or release checksum.
Supported kinds:
launched;sunk;session-extended(manual, withextendedByMinutesand UTCexpiresAt);reaper-warning;reaper-destroyed;seed-restored;heartbeat-recovered.
Supported reasons are manual, idle, session-expired, provider, health, seed, and
unknown.
Suite runs
{
"version": "1",
"environmentSlug": "demo-harbor",
"suiteName": "checkout-e2e",
"runs": [
{
"sourceRunId": "run_demo_20260723_001",
"specName": "create-order",
"status": "passed",
"durationMs": 18420,
"metricName": "workflow-latency",
"metricMs": 1220,
"variant": "baseline",
"artifacts": [
{
"$schema": "urn:filecheap.dev:artifact-ref:v1",
"version": 1,
"provider": "fcheap-local",
"uri": "fcheap://stash/report_20260723_184500.123456789_0123456789abcdef01234567",
"artifact_id": "report_20260723_184500.123456789_0123456789abcdef01234567",
"kind": "cairntrace.run",
"producer": {
"tool": "cairntrace",
"native_schema": "urn:cairntrace.dev:run:v1",
"native_id": "run_demo_20260723_001",
"entrypoint": "run.json"
}
}
]
}
]
}
A request accepts up to 250 runs; each run accepts up to 20 unique
ArtifactRefV1 envelopes. The server validates the exact schema, provider
identity rules, stable URLs, safe producer metadata, limits, status values,
and duplicates before persisting data.
ArtifactRefV1 is the canonical contract for every new producer. During the
compatibility release, this endpoint also accepts only the exact historical
{kind, store: "fcheap-local", stashId} and
{kind, store: "link", url} locators and normalizes them immediately to V1.
Unknown fields, unsafe URLs, invalid IDs, and identities that collide after
conversion remain invalid. This legacy transport path is deprecated and will
be removed after all deployed producers use ArtifactRefV1 and historical
pre-fingerprint rows have been remediated.
Fleet
GET /api/fleet returns:
{
"mode": "demo",
"generatedAt": "2026-07-23T18:21:00.000Z",
"totals": {
"environments": 1,
"afloat": 1,
"stale": 0,
"sunk": 0,
"unknown": 0,
"estimatedHourlyUsd": 0.04,
"estimatedMonthToDateUsd": 0.62
},
"environments": []
}
IP addresses, names, and stack details belong only to the authenticated response. Public metadata does not include them.
Operational probes
GET /api/health is an unauthenticated liveness probe. It returns only:
{ "status": "ok" }
GET /api/ready validates the runtime contract and, in database mode, runs a
bounded database probe. It requires Authorization: Bearer <CRON_SECRET> and
returns { "status": "ready" }, or a redacted 503 response. Neither endpoint
returns configuration, provider inventory, or fleet data.
Login
URL-encoded form:
token=<admin-token>
next=/app
next is optional and accepts only relative paths that begin with /, never
//. Successful authentication creates the chalupa_admin HttpOnly cookie.
Do not send the token in a query string.
Errors
The API uses semantic HTTP status codes:
| Code | Case |
|---|---|
202 |
Ingest accepted. |
400 |
Malformed JSON. |
401 |
Invalid session or signature. |
409 |
HMAC nonce already claimed, deployment assigned to another environment, telemetry waiting for launch registration (telemetry_deployment_unavailable), an ended deployment outside its final grace period (telemetry_deployment_ended), a Monitor producer version that differs from the deployment pin (telemetry_monitor_version_mismatch), or a run/window identity retried with different normalized content (run_ingest_conflict or telemetry_ingest_conflict). |
413 |
Body exceeds the endpoint limit, or a deployment has exhausted its bounded log or telemetry storage quota. |
422 |
Invalid content type, UTF-8, or schema. |
503 |
Writes deliberately disabled in demo mode. |
Messages must never reveal keys, expected signatures, or sensitive content.
Inference summaries
POST /api/ingest/inference accepts a signed JSON body of at most 16 KiB. It uses the existing HMAC, environment-key binding and nonce protection. Its strict body contains version: "1", environmentSlug, deployment: { provider: "digitalocean", providerId }, and summary.
summary contains sourceRunId, model, optional digest, contextSize, status, stopReason, truncated, promptTokens, evalTokens, toolCalls, toolErrors, toolCallsOmitted, and optional timing. Timing fields are ttftMs, loadMs, promptEvalMs, evalMs and totalMs. Status is settled, canceled, failed or not_admitted; it never represents a test-quality verdict.
The deployment must already belong to the authenticated organization and environment. An accepted report returns HTTP 202 with accepted, deduplicated and an id. A different summary under the same deployment and source ID returns HTTP 409. Late receipts do not reactivate ended deployments. Receipt text, errors, tool arguments, tool output and paths are not accepted. Use chalupa inference report --dry-run to inspect the projection before sending it.
Heartbeats optionally include gpu: { state, devices }. State is observed or unavailable; unavailable requires an empty device list. Each of at most eight devices has id, name, and optional utilizationPercent, memoryUsedMiB, memoryTotalMiB, temperatureC and powerWatts. Missing sensor fields are unknown rather than zero. These samples follow the existing 48-hour heartbeat retention.
Account provider token
These endpoints require Authorization: Bearer <account session token> from
hosted account authentication. They use the session's default organization,
regardless of the organization cookie. No organization returns HTTP 400 with
error.code: "organization_required"; missing or inactive authentication returns
401. Only digitalocean is supported. Responses use Cache-Control: no-store.
| Method | Path | Result (HTTP 200) |
|---|---|---|
GET |
/api/account/provider-token?provider=digitalocean |
{ provider, status, createdAt?, rotatedAt?, reaper } |
PUT |
/api/account/provider-token |
{ provider: "digitalocean", status: "active", reaper: "armed", scopesRequired: ["droplet:read", "droplet:delete"] } |
DELETE |
/api/account/provider-token?provider=digitalocean |
{ provider: "digitalocean", status: "revoked", reaper: "not-armed" } |
GET status is active, none, revoked, or invalid; reaper is armed only
for an active credential. Dates are ISO 8601 strings. No response returns token
material. PUT accepts { "provider": "digitalocean", "token": "your_digitalocean_token_here" }
and permits five attempts per account session per ten-minute window (429 after
that). Invalid shapes return 422. The body limit is 2 KiB.
PUT checks GET https://api.digitalocean.com/v2/droplets?tag_name=chalupa&per_page=1
with a ten-second timeout. DigitalOcean 401/403 returns HTTP 400:
{
"code": "token_rejected",
"detail": "DigitalOcean refused the token; it needs the custom scopes droplet:read and droplet:delete"
}
Network failures and other unsuccessful provider responses return 503 with
code: "provider_unavailable". This read probe cannot prove delete permission;
select both custom scopes in DigitalOcean. A rejected delete later disarms the
credential and records a reaper-warning with reason provider.
This is opt-in. The token can read and delete droplets throughout its
DigitalOcean account. Chalupa uses it only to destroy Chalupa-tagged droplets
registered to that organization after session expiry or an idle deadline. The
token is encrypted at rest with AES-256-GCM under CHALUPA_INGEST_MASTER_KEY
and never shown again. PUT replaces the stored credential and records
reaper-armed; DELETE marks it revoked and records a manual reaper-warning
when a row exists. Delete the token in DigitalOcean for instant provider-side
revocation. Console revocation cannot cancel an already-started provider request.
Managed GPU account API
Both endpoints use an account session in Authorization: Bearer <token> and the
account's first customer organization (the internal platform organization is never a
billing target). X-Chalupa-Organization: <slug or id> picks another organization the
account belongs to. Cookies do not select the organization. They
return 401 without authentication, 400 organization_required without a default
organization, and 403 for the internal platform organization.
GET /api/account/managed/balance
Available to every customer organization, including Community. Returns 200:
{
"enabled": true,
"currency": "usd",
"balanceCents": 0,
"updatedAt": null,
"plan": { "key": "community", "eligible": false },
"topUpOptionsCents": [1000, 2500, 5000],
"rates": [
{ "tier": "gpu-small", "gpu": "RTX 4000 Ada", "vramGiB": 20, "providerListCentsPerHour": 76, "centsPerHour": 95 },
{ "tier": "gpu-large", "gpu": "RTX 6000 Ada", "vramGiB": 48, "providerListCentsPerHour": 157, "centsPerHour": 197 },
{ "tier": "gpu-max", "gpu": "H100", "vramGiB": 80, "providerListCentsPerHour": 441, "centsPerHour": 552 }
],
"storageCentsPerGibMonth": 13,
"ledger": []
}
Ledger entries contain id, kind, deltaCents, balanceAfterCents, createdAt,
and nullable note. Dates are ISO timestamps; updatedAt is null before the
first credit. The response never includes Stripe identifiers. Demo reads return
zero balance and an empty ledger.
POST /api/account/managed/top-up
Body: { "amountCents": 2500 }. Only integer amounts 1000, 2500, and 5000 are
accepted; invalid bodies return 400. Without an active Solo or Crew entitlement,
the endpoint returns 403 { "code": "plan_required", "detail": "Managed GPU needs an active Solo or Crew plan" }.
Top-up attempts are limited to five per organization per ten-minute window;
excess attempts return 429. Demo writes are disabled.
Success returns 200 { "url": "https://checkout.stripe.com/...", "amountCents": 2500, "currency": "usd" }.
Follow url to pay. The balance changes only after a verified paid Stripe webhook,
not when Checkout is created or the browser returns. No compute is provisioned.
Managed launch jobs
Account routes require an account session bearer token and use its default organization. Requests are strict JSON objects; unknown fields fail validation.
| Endpoint | Request | Success |
|---|---|---|
POST /api/account/managed/launch |
Launch specification below plus environmentSlug |
202 { jobId, status: "queued", estimateCents, centsPerHour, expiresAfterMinutes, environmentSlug } |
GET /api/account/managed/jobs/{id} |
UUID path parameter | 200 job view below; 404 for missing/other-organization jobs |
POST /api/account/managed/down |
{ environmentSlug } |
202 { jobId } |
POST /api/account/managed/allow |
{ environmentSlug, allowIpv4Cidr } |
202 { jobId } |
GET /api/account/managed/hosts |
None | 200 { hosts, jobs } |
GET /api/account/managed/capacity |
None | 200 { capacity: { [sizeSlug]: regions[] } | null, checkedAt: ISO8601 | null } |
Launch specification:
| Field | Constraint/default |
|---|---|
environmentSlug |
3–63 lowercase letters, digits or hyphens; starts/ends alphanumeric |
tier |
gpu-small, gpu-large, gpu-max |
region |
tor1 (default), nyc2, ams3, atl1 |
model |
1–128 letters, digits, dots, underscores, colons, slashes or hyphens |
contextSize |
"auto" or integer 2048–262144; default "auto" |
cacheGb |
Integer 20–500; default 60 |
expiresAfterMinutes |
Integer 15–480 |
publicKey |
OpenSSH ssh-ed25519 key, single line, optional comment up to 64 characters |
allowIpv4Cidr |
One IPv4 address with /32 |
agent |
Optional opencode or omp |
Launch checks plan eligibility (403 plan_required), concurrent host cap (409
host_cap), then balance (402 { code: "insufficient_balance", requiredCents, balanceCents, topUpOptionsCents }), then current GPU capacity. A pending job on the same environment returns
409 already_queued. Launch rate limit is ten attempts per organization per
minute (429). Down and allow reuse the last launch specification; no active host
or running launch returns 404 no_host.
Capacity pre-flight
After balance validation, a known unavailable tier/region returns HTTP 409:
{
"code": "no_capacity",
"requested": { "tier": "gpu-large", "region": "tor1", "gpu": "RTX 6000 Ada" },
"alternatives": [
{ "tier": "gpu-small", "region": "tor1", "gpu": "RTX 4000 Ada", "vramGiB": 20, "centsPerHour": 95, "fitsModel": false },
{ "tier": "gpu-large", "region": "nyc2", "gpu": "RTX 6000 Ada", "vramGiB": 48, "centsPerHour": 197, "fitsModel": true },
{ "tier": "gpu-max", "region": "tor1", "gpu": "H100", "vramGiB": 80, "centsPerHour": 552, "fitsModel": true }
],
"checkedAt": "2026-09-07T12:00:00.000Z"
}
No job, deployment or balance entry is created for this response. The existing
launch attempt rate limiter still applies. Alternatives include the same tier in
other regions and other tiers in the requested region and elsewhere, limited to
supported managed regions. They sort by centsPerHour, then prefer the requested
region, then sort regions alphabetically. fitsModel compares the estimate for
spec.model with the tier's VRAM; unknown estimates and hosted cloud tags are false.
GET /api/account/managed/capacity requires the same account session bearer token.
It returns GPU size slugs mapped to current region arrays, with checkedAt from
the probe. An empty region array means unavailable; capacity: null and
checkedAt: null mean unknown. The console uses DIGITALOCEAN_WRITE_TOKEN to query
GET https://api.digitalocean.com/v2/sizes?per_page=200, with a 10-second timeout
and a 60-second in-memory cache. HTTP, network, parse and incomplete-page failures
are unknown and never block launches. The account response is Cache-Control: no-store.
Capacity is advisory and may change before the worker provisions the host.
The job view contains id, kind, status, environmentSlug, spec, result, error, createdAt, finishedAt, deployment. Its spec omits publicKey but retains
allowIpv4Cidr. Deployment is { id, sessionExpiresAt } or null. Result is null,
a progress object with log, or the successful connection result plus log.
Log arrays retain the latest 200 lines, each at most 500 characters.
Hosts contain id, slug, ip, providerId, sessionExpiresAt, sizeSlug, region, launchedAt. The last ten job summaries contain id, kind, status, environmentSlug, createdAt, finishedAt, error.
Internal managed worker API
These routes accept only Authorization: Bearer <CLOUD_ADMIN_TOKEN>; account
sessions and admin cookies are insufficient. Invalid credentials return 401.
Demo-mode writes return 503 demo_read_only.
| Endpoint | Request | Response |
|---|---|---|
POST /api/internal/managed/jobs/claim |
{ workerId, version } |
200 { job } with full spec including SSH public key and CIDR, or 204 when empty |
POST /api/internal/managed/jobs/{id}/progress |
{ workerId, message } |
200 { ok: true } |
POST /api/internal/managed/jobs/{id}/complete |
{ workerId, ok: true, result } or { workerId, ok: false, error } |
200 { ok: true } |
POST /api/internal/managed/heartbeat |
{ workerId, version, currentJobId? } |
200 { ok: true } |
workerId matches ^[a-z0-9-]{3,64}$; version is 1–64 characters.
Successful result requires ip, hostKey, hostKeyFingerprint, providerId, priceHourly, sizeSlug, region; priceHourly is the provider's hourly USD price.
Completion and progress require the running job's owner, otherwise 409
not_owner. Error storage is capped at 2000 characters and DigitalOcean
dop_v1_ tokens are redacted. Successful launch results are converted server-side
to the existing lifecycle ingest contract; workers cannot choose the organization.
GET /api/cron/managed-jobs requires the cron bearer secret, recovers stale jobs,
checks worker availability, and returns { recovered }. It runs every five minutes.
Managed ingest key
POST /api/account/managed/ingest-key requires a bearer account session.
The organization comes from requireManagedAccount, including its existing
X-Chalupa-Organization membership selection; a body organization is rejected.
Send { "environmentSlug": "my-gpu" } (the managed slug rules apply).
Returns 200 { ingestKeyId, ingestKey, environmentSlug } with Cache-Control: no-store.
The plaintext key is returned only at issuance. No hash or encrypted envelope is returned.
Keys use the existing admin ingest repository's env_<slug>_<random> public ID
and 43-character base64url HMAC material, stored encrypted at rest. The label is
managed:<environmentSlug>; issuance atomically revokes previous keys with that
label in the same organization. Other labels and organizations are unaffected.
Requests are limited to five per minute per organization using IngestAttemptStore.
Authentication failures return 401, invalid bodies 422, throttling 429, and demo
writes 503. Deliver the material to /etc/chalupa/ingest.key and the public ID as
CHALUPA_INGEST_KEY_ID in the host's agent environment.