Internal API
This is the machine-facing API that connects the edge Workers to AVERASE: the engine Worker verifies a device token at the edge, then forwards each call here with an HMAC signature. Customers never call these endpoints directly — the erasure engine and hotswap station drive them through the Worker — but the contract is documented in full because it defines everything a wipe session produces, and because the sandbox mirrors it endpoint-for-endpoint so you can exercise the whole flow locally.
Every endpoint below is available in the sandbox at https://api.averase.com with the same paths, bodies, and status codes — see Sandbox endpoints. In the sandbox, HMAC signing is optional (enabled by setting INTERNAL_API_SECRET); in production it is mandatory.
Authentication
Every request to /internal/* must carry two headers:
- Name
X-Averase-Timestamp- Type
- string
- Description
Unix time in seconds (digits only, up to 12). Requests are rejected when the timestamp is more than ±300 seconds from server time.
- Name
X-Averase-Signature- Type
- string
- Description
sha256=followed by the lowercase hex HMAC-SHA256 of the string"{timestamp}.{raw body}", keyed with the shared internal API secret. The signature covers the exact raw request bytes — sign the body you send, not a re-serialization of it.
A missing header, a stale timestamp, or a signature mismatch returns 401:
{ "error": "unauthorized" }
If the server has no internal API secret configured, the entire /internal/* namespace answers 503 — the API fails closed rather than accepting unsigned traffic.
Signing a request
TS=$(date +%s)
BODY='{"jti":"0e6f4a8c-2d1b-4e5f-9a7c-8b6d5e4f3a2b","account_id":"acct_demo"}'
SIG=$(printf '%s.%s' "$TS" "$BODY" \
| openssl dgst -sha256 -hmac "$INTERNAL_API_SECRET" -hex | awk '{print $NF}')
curl -X POST https://averase.com/internal/tokens/verify \
-H "Content-Type: application/json" \
-H "X-Averase-Timestamp: $TS" \
-H "X-Averase-Signature: sha256=$SIG" \
-d "$BODY"
Deny reasons
The two /internal/tokens/* endpoints run the full license-token decision ladder, evaluated strictly in this order — the first match wins:
| Reason | Meaning |
|---|---|
disabled | MAESON staff kill switch. Outranks every other status; account admins cannot lift it. |
revoked | The account admin revoked the token. Permanent unless the admin reinstates it. |
suspended | The account admin suspended the token. Reversible from the ISO control column. |
account_inactive | The account's subscription has lapsed. |
quota_exceeded | The token's use_count has reached its quota_limit. |
A deny returns 403; an unknown jti — or a supplied account_id that doesn't match the token's account — returns 404. The other endpoints (assets, wipe_results, chassis_maps) do not run the full ladder: they check only that the token's status is active, answering 403 with {"error": "token revoked"} (or suspended/disabled) otherwise.
Verify a token
The boot-unlock check. The kiosk boots into a hard lock and calls this once it is online; only an affirmative allow unlocks the UI, for that boot. It applies the same decision ladder as check-in but is unmetered — booting isn't wiping, so it never touches quota. On allow it only updates the token's last_seen_at (a heartbeat).
Request body
- Name
jti- Type
- string
- Description
The license token's UUID (the
jticlaim from the device token).
- Name
account_id- Type
- string
- Description
Optional cross-tenant defense. When present, a mismatch with the token's account returns
404— never a silently widened lookup.
Responses
200— allowed.accountis the account's display name;quota_remainingisnullwhen the token is unlimited.403— denied, with areasonfrom the table above. Fires thewipe_session_blockedwebhook with phase"verify".404— unknown token or mismatchedaccount_id.
Note the asymmetry with check-in: verify returns account, validate returns job_id — never both.
Request
curl -X POST https://averase.com/internal/tokens/verify \
-H "Content-Type: application/json" \
-H "X-Averase-Timestamp: $TS" \
-H "X-Averase-Signature: sha256=$SIG" \
-d '{"jti":"0e6f4a8c-2d1b-4e5f-9a7c-8b6d5e4f3a2b","account_id":"acct_demo"}'
200 — allowed
{
"allow": true,
"status": "active",
"account": "Acme ITAD, LLC",
"quota_remaining": null
}
403 — denied
{
"allow": false,
"status": "revoked",
"reason": "revoked",
"quota_remaining": 0
}
404 — unknown
{
"allow": false,
"status": "unknown",
"reason": "not_found"
}
Check in before a wipe
The metered pre-wipe check-in. Same request body and same decision ladder as verify, with two differences: an allow atomically increments the token's use_count (a single-writer SQL increment, so concurrent check-ins can't double-spend quota), and the response carries a fresh job_id — the UUID that ties the eventual wipe result back to this check-in and makes wipe-result submission idempotent.
quota_remaining is computed after the increment; null means unlimited.
This is the fail-closed gate: a deny, an unprovisioned stick, or any network failure blocks the wipe. On allow it fires the wipe_session_ready webhook; on deny, wipe_session_blocked with phase "checkin".
Request body
- Name
jti- Type
- string
- Description
The license token's UUID.
- Name
account_id- Type
- string
- Description
Optional cross-check, as on verify.
Responses
200— allowed, quota consumed,job_idissued.403/404— same shapes and reasons as verify (phase"checkin"on the blocked webhook).
Request
curl -X POST https://averase.com/internal/tokens/validate \
-H "Content-Type: application/json" \
-H "X-Averase-Timestamp: $TS" \
-H "X-Averase-Signature: sha256=$SIG" \
-d '{"jti":"0e6f4a8c-2d1b-4e5f-9a7c-8b6d5e4f3a2b","account_id":"acct_demo"}'
200 — allowed
{
"allow": true,
"status": "active",
"quota_remaining": 1,
"job_id": "0e6f4a8c-2d1b-4e5f-9a7c-8b6d5e4f3a2b"
}
403 — denied
{
"allow": false,
"status": "revoked",
"reason": "revoked",
"quota_remaining": 0
}
Report an asset
The once-per-boot hardware report: the complete serialized fingerprint plus internal storage for the machine the kiosk booted on. This is what populates /webapp/inventory.
Upsert semantics
Assets are upserted one row per physical machine per account, keyed by hardware_uid = [system.uuid, system.serial].join("/") (falling back to record_id when DMI identity is missing). The first sighting records who reported it (user, license token, ISO download) and first_seen_at; every subsequent boot updates record_id, engine_version, the full fingerprint document, and last_booted_at.
Alongside the raw document, a set of promoted columns is refreshed on every boot for grids and the public API: manufacturer, model, serial_number, board_model/board_serial, asset_tag, bios_version, tpm_version, firmware_mode, secure_boot, cpu_model/cpu_cores/cpu_threads, memory_total_bytes, gpu, storage_summary, storage_serials, and battery_health_pct (the minimum across batteries).
Fires the asset_reported webhook on the machine's first boot only — repeat boots update the stored asset silently.
Request body
- Name
jti- Type
- string
- Description
The license token's UUID. Required.
- Name
record_id- Type
- string
- Description
The boot record id, e.g.
CW-20260726-210639. Required.
- Name
engine_version- Type
- string
- Description
The engine build reporting the machine.
- Name
fingerprint- Type
- object
- Description
The complete hardware document:
system(manufacturer,product,serial,uuid),cpu,storage,memory,batteries, and the rest of the collected sections. Required.
Responses
201— upserted (created or refreshed).403—{"error": "token revoked"}(orsuspended/disabled).404—{"error": "unknown token"}.422—{"error": "missing record_id or fingerprint"}.
Request
curl -X POST https://averase.com/internal/assets \
-H "Content-Type: application/json" \
-H "X-Averase-Timestamp: $TS" \
-H "X-Averase-Signature: sha256=$SIG" \
-d '{
"jti": "0e6f4a8c-2d1b-4e5f-9a7c-8b6d5e4f3a2b",
"record_id": "CW-20260812-000117",
"engine_version": "1.0.0",
"fingerprint": {
"system": {
"manufacturer": "Dell Inc.",
"product": "Latitude 5540",
"serial": "74HRKY3",
"uuid": "4c4c4544-0034-5a10-8052-b9c04f4b5933"
},
"cpu": { "model": "13th Gen Intel(R) Core(TM) i5-1335U" },
"memory": { "total_bytes": 17179869184 },
"storage": [
{
"model": "PM9B1 NVMe Samsung 512GB",
"serial": "S6MZNF1WA04527"
}
]
}
}'
201 — upserted
{
"ok": true,
"asset_id": "asset_Mlrnd05DOeK6qILJp1gE9bN3"
}
422
{ "error": "missing record_id or fingerprint" }
Attach operator notes
Attaches the kiosk operator's free-text notes to an already-reported asset, addressed by the boot's record_id. Fires no webhook.
Request body
- Name
jti- Type
- string
- Description
The license token's UUID.
- Name
record_id- Type
- string
- Description
The boot record id of the asset the notes belong to.
- Name
notes- Type
- string
- Description
The operator's notes.
Responses
200— saved.403— token not active.404—{"error": "unknown token"}or{"error": "unknown asset"}(the boot's asset report must land first).
Request
curl -X POST https://averase.com/internal/assets/notes \
-H "Content-Type: application/json" \
-H "X-Averase-Timestamp: $TS" \
-H "X-Averase-Signature: sha256=$SIG" \
-d '{
"jti": "0e6f4a8c-2d1b-4e5f-9a7c-8b6d5e4f3a2b",
"record_id": "CW-20260812-000117",
"notes": "Chassis dent on left corner; otherwise clean."
}'
200
{
"ok": true,
"asset_id": "asset_Mlrnd05DOeK6qILJp1gE9bN3"
}
Submit hardware test results
Records the kiosk's hardware test results against the asset. Each submission replaces the whole snapshot — send the complete set every time, not a delta. Fires the hardware_tests_completed webhook.
Wire ids and statuses
tests is an object keyed by wire id, each value { "status": ..., "detail": ... }:
| Wire id | Statuses |
|---|---|
keyboard, display, usb_ports, battery, wifi_adapter, bluetooth, webcam, audio, touchscreen | passed, failed, not_started, not_applicable (plus detected for detection-only checks) |
Request body
- Name
jti- Type
- string
- Description
The license token's UUID.
- Name
record_id- Type
- string
- Description
The boot record id of the tested asset.
- Name
tests- Type
- object
- Description
Map of wire id →
{status, detail}.detailis optional free text (e.g. a failure note).
Responses
200— snapshot replaced.403/404— as on notes.422—{"error": "invalid tests"}.
Request
curl -X POST https://averase.com/internal/assets/tests \
-H "Content-Type: application/json" \
-H "X-Averase-Timestamp: $TS" \
-H "X-Averase-Signature: sha256=$SIG" \
-d '{
"jti": "0e6f4a8c-2d1b-4e5f-9a7c-8b6d5e4f3a2b",
"record_id": "CW-20260812-000117",
"tests": {
"keyboard": { "status": "passed" },
"webcam": { "status": "failed", "detail": "no image" },
"touchscreen": { "status": "not_applicable" }
}
}'
200
{
"ok": true,
"asset_id": "asset_Mlrnd05DOeK6qILJp1gE9bN3"
}
Submit a wipe result
One POST per erased drive, carrying the full evidence of the wipe. The endpoint is idempotent on job_id: the first receipt creates the record and returns 201; a re-POST with the same job_id returns 200 with the existing record and fires nothing again. That makes retries safe — the engine can resend after a network blip without duplicating results, quota, or certificates.
On first receipt only, when the result is certifiable — wipe_status absent or completed, and validation absent or approved — AVERASE enqueues certificate generation (a signed PDF uploaded to private storage) and fires the wipe_session_completed webhook. Non-certifiable results are persisted and listed on /webapp/wipes but never certify.
Request body
- Name
jti- Type
- string
- Description
The license token's UUID. Required.
- Name
job_id- Type
- string
- Description
The UUID issued by
/internal/tokens/validate. Required — the idempotency key.
- Name
drive_serial- Type
- string
- Description
Serial number of the erased drive.
- Name
drive_model- Type
- string
- Description
Drive model string.
- Name
drive_capacity_gb- Type
- integer
- Description
Capacity in gigabytes (decimal).
- Name
wipe_method- Type
- string
- Description
The sanitization method, e.g.
NIST 800-88 R2 Purge.
- Name
passes_completed- Type
- integer
- Description
Overwrite passes completed;
nullfor cryptographic and block erase.
- Name
started_at- Type
- timestamp
- Description
Wipe start time (ISO-8601).
- Name
completed_at- Type
- timestamp
- Description
Wipe finish time (ISO-8601).
- Name
verification_hash- Type
- string
- Description
SHA-256 over the re-read verification bytes.
- Name
wipe_status- Type
- string
- Description
completed,failed, orcancelled. Absent reads as legacycompleted.
- Name
technique- Type
- string
- Description
The concrete technique used, e.g.
NVMe Sanitize — crypto erase.
- Name
validation- Type
- string
- Description
approvedorrejected; absent reads as approved.
- Name
disposition- Type
- string
- Description
sanitized,review, ordestroy.
- Name
markings_removed- Type
- boolean
- Description
Whether media markings were removed.
- Name
tool_components- Type
- string
- Description
Tooling used, e.g.
nvme-cli 2.4.
- Name
verification_result- Type
- string
- Description
Human-readable verification outcome, e.g.
Pass — 32/32 fingerprint sectors overwritten.
Responses
201— first receipt; certificate generation enqueued when certifiable.200— duplicatejob_id; the existing record, no side effects.403— token not active.404—{"error": "unknown token"}.422—{"error": "missing job_id"}.
certificate_status in the response is the current state of the certificate pipeline (pending, generating, complete, failed) — don't poll it; the certificate_generated webhook announces completion.
Request
curl -X POST https://averase.com/internal/wipe_results \
-H "Content-Type: application/json" \
-H "X-Averase-Timestamp: $TS" \
-H "X-Averase-Signature: sha256=$SIG" \
-d '{
"jti": "0e6f4a8c-2d1b-4e5f-9a7c-8b6d5e4f3a2b",
"job_id": "0e6f4a8c-2d1b-4e5f-9a7c-8b6d5e4f3a2b",
"drive_serial": "S64FNE2R504071",
"drive_model": "SAMSUNG MZVL2512HDJD-00BL2",
"drive_capacity_gb": 512,
"wipe_method": "NIST 800-88 R2 Purge",
"passes_completed": null,
"started_at": "2026-08-12T17:03:01Z",
"completed_at": "2026-08-12T17:40:58Z",
"verification_hash": "1f7c9d0a4b2e8f6c3a5d7e9b1c4f8a2d6e0b3c7f9a1d5e8b2c6f0a4d7e9b1c4f",
"wipe_status": "completed",
"technique": "NVMe Sanitize — crypto erase",
"validation": "approved",
"disposition": "sanitized",
"markings_removed": true,
"tool_components": "nvme-cli 2.4",
"verification_result": "Pass — 32/32 fingerprint sectors overwritten"
}'
201 — first receipt
{
"ok": true,
"wipe_result_id": "wr_01J4R2M8K3P7QS5TV9WX6YZABC",
"certificate_status": "pending"
}
422
{ "error": "missing job_id" }
Save a chassis map
Saves the hotswap station's bay map — the mapping-wizard output that ties physical bay labels to controller lanes — keyed to the chassis by its DMI identity (system.uuid + system.serial). The endpoint first records the chassis itself as an asset (so the chassis appears in /webapp/inventory), then stores the map.
Bay normalization
Each entry in map.bays must have a non-blank lane and label; entries missing either are dropped. An optional connector is kept only when it is an object. If normalization leaves no usable bays, the request fails with 422.
Request body
- Name
jti- Type
- string
- Description
The license token's UUID. Required.
- Name
record_id- Type
- string
- Description
The boot record id. Required.
- Name
engine_version- Type
- string
- Description
The station build saving the map.
- Name
fingerprint- Type
- object
- Description
The chassis hardware document — its
systemDMI section supplies the identity key. Required.
- Name
map- Type
- object
- Description
{ "name": ..., "bays": [{ "lane", "label", "connector"? }] }.
Responses
201— map saved (and chassis asset upserted).403/404— as elsewhere.422—{"error": "missing record_id or fingerprint"}or{"error": "map has no usable bays"}.
Request
curl -X POST https://averase.com/internal/chassis_maps \
-H "Content-Type: application/json" \
-H "X-Averase-Timestamp: $TS" \
-H "X-Averase-Signature: sha256=$SIG" \
-d '{
"jti": "0e6f4a8c-2d1b-4e5f-9a7c-8b6d5e4f3a2b",
"record_id": "CW-20260812-000117",
"engine_version": "1.0.0",
"fingerprint": {
"system": {
"manufacturer": "Supermicro",
"product": "SSG-6029P",
"serial": "S424521X9B00432",
"uuid": "9c7e2f10-44aa-4b7d-9d3e-1f2a3b4c5d6e"
}
},
"map": {
"name": "Rack 2 shelf A",
"bays": [
{ "lane": "05:00.0/phy14", "label": "Bay 1" },
{ "lane": "05:00.0/phy15", "label": "Bay 2" }
]
}
}'
201 — saved
{
"ok": true,
"chassis_map_id": "cmap_01J4R5P0M6S9UV8WX2YZ3ABCDE",
"asset_id": "asset_01J4R5NZ7Q4T6VW9XA1BC2DEFG"
}
422
{ "error": "map has no usable bays" }
Look up a chassis map
Fetches a previously saved bay map by chassis DMI identity — the hotswap station calls this at every boot so bay labels survive re-imaging. It is a read modelled as a POST because the HMAC scheme signs the request body.
Request body
- Name
jti- Type
- string
- Description
The license token's UUID.
- Name
system- Type
- object
- Description
The chassis DMI identity:
{ "uuid", "serial" }.
Responses
Always 200: {"found": true, "map": {...}} when a map exists for this account and chassis, {"found": false} otherwise — a station booting an unmapped chassis simply runs the mapping wizard.
Request
curl -X POST https://averase.com/internal/chassis_maps/lookup \
-H "Content-Type: application/json" \
-H "X-Averase-Timestamp: $TS" \
-H "X-Averase-Signature: sha256=$SIG" \
-d '{
"jti": "0e6f4a8c-2d1b-4e5f-9a7c-8b6d5e4f3a2b",
"system": {
"uuid": "9c7e2f10-44aa-4b7d-9d3e-1f2a3b4c5d6e",
"serial": "S424521X9B00432"
}
}'
200 — found
{
"found": true,
"map": {
"name": "Rack 2 shelf A",
"bays": [
{ "lane": "05:00.0/phy14", "label": "Bay 1" },
{ "lane": "05:00.0/phy15", "label": "Bay 2" }
]
}
}
200 — not found
{ "found": false }
Related pages
- Sandbox — run this whole API locally, mint test tokens, and watch webhooks fire.
- License tokens — the entitlement model behind verify and validate.
- Certificates — what happens after a wipe result lands.