EAS Audit Lite — API documentation
Hash-chained tamper-evident audit log API. All requests authenticate with a Bearer token. Base URL: https://audit.eliteagenticsolutions.com
Authentication
Every endpoint requires a Bearer token in the Authorization header. You receive your API key via email after Stripe checkout.
Authorization: Bearer empire_audit_xxxxxxxxxxxxxxxxxxxxxx
Treat the key like a password. We hash it server-side and only show it once.
Write an event
POST/audit/v1/events
Append an event to the tenant's hash chain. Returns the event's position, hash, and previous-hash link.
Request body:
| Field | Type | Required | Notes |
|---|---|---|---|
event_type | string | yes | e.g. user.login, config.change, order.placed |
payload | object | yes | Arbitrary JSON. Stored as-is and hashed. |
ts_ms | integer | no | Unix milliseconds. Defaults to now. |
curl -X POST 'https://audit.eliteagenticsolutions.com/audit/v1/events' \
-H "Authorization: Bearer empire_audit_xxx" \
-H "Content-Type: application/json" \
-d '{
"event_type": "user.login",
"payload": {"user_id":"u123","ip":"1.2.3.4","ua":"chrome/130"}
}'
Response:
{
"event_id": 42,
"chain_position": 42,
"entry_hash": "a3f4ce6b...",
"prev_hash": "b89c1d2e...",
"payload_hash": "7d12af90...",
"ts_ms": 1730000000123,
"tenant_id": "cus_..."
}
Query events
GET/audit/v1/events
List events, newest first. Pagination via cursor (last seen chain_position).
| Query param | Notes |
|---|---|
from | Lower bound on ts_ms (inclusive) |
to | Upper bound on ts_ms (inclusive) |
event_type | Exact match filter |
limit | Max 1000 (default 100) |
cursor | Skip rows with chain_position <= cursor |
curl 'https://audit.eliteagenticsolutions.com/audit/v1/events?event_type=user.login&limit=50' \
-H "Authorization: Bearer empire_audit_xxx"
Get a single event
GET/audit/v1/events/{event_id}
Returns one event with on-the-fly hash re-verification. Response includes integrity_ok boolean.
curl 'https://audit.eliteagenticsolutions.com/audit/v1/events/42' \
-H "Authorization: Bearer empire_audit_xxx"
Verify the chain
GET/audit/v1/chain/verify
Walk every event from genesis and check hash continuity. Returns OK or pinpoints the break.
curl 'https://audit.eliteagenticsolutions.com/audit/v1/chain/verify' \
-H "Authorization: Bearer empire_audit_xxx"
# Success:
{"status": "OK", "checked": 12345, "tenant_id":"cus_xx", "head_hash": "..."}
# Break:
{"status": "BREAK", "break_at_position": 1234, "reason":"entry_hash_mismatch", "checked": 1233}
Export the chain
POST/audit/v1/export?fmt=json|csv
Dump the full chain (or a time-bounded slice) for compliance review.
curl -X POST 'https://audit.eliteagenticsolutions.com/audit/v1/export?fmt=csv&from=1700000000000' \
-H "Authorization: Bearer empire_audit_xxx" \
-o audit-export.csv
Check your usage
GET/audit/v1/usage
Current month's event count, tier limits, storage usage.
{
"tier": "starter",
"events_this_month": 12345,
"events_cap_monthly": 100000,
"events_stored_total": 250000,
"events_stored_cap": 1000000,
"rate_per_min": 60,
"features": ["api","query","verify","export"]
}
Slack alert integration (Growth + Scale)
Send real-time alerts to a Slack channel when audit events of selected types are written. Configure once, then alerts fire fire-and-forget on every matching event-write.
POST/audit/v1/integrations/slack
Body:
| Field | Type | Notes |
|---|---|---|
webhook_url | string | Slack incoming-webhook URL (e.g. https://hooks.slack.com/services/T0/B0/XXXXX) |
alert_event_types | array<string> | Which event_type values trigger alerts. Use ["*"] to alert on every event. Events tagged payload.alert=true also fire regardless of this filter. |
Returns 200 on success. Returns 400 if the test POST to Slack fails. Returns 402 for Starter tier.
curl -X POST 'https://audit.eliteagenticsolutions.com/audit/v1/integrations/slack' \
-H "Authorization: Bearer empire_audit_xxx" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://hooks.slack.com/services/T000/B000/XXXXXXXX",
"alert_event_types": ["chain.tampered", "user.deleted", "config.change"]
}'
GET/audit/v1/integrations/slack
Returns current config (webhook URL is redacted). Returns 402 for Starter.
DELETE/audit/v1/integrations/slack
Removes the webhook config. Alerts stop firing immediately.
Alert message format:
{
"text": ":bell: EAS Audit Alert: chain.tampered",
"blocks": [
{
"type": "section",
"text": {"type": "mrkdwn", "text": "*Event*: `chain.tampered`\n*Customer*: you@example.com\n*Time*: 2026-05-10T01:54:15Z\n*Chain Position*: #42"}
},
{
"type": "section",
"text": {"type": "mrkdwn", "text": "*Payload*:\n```{...}```"}
}
]
}
Outbound webhooks (Scale)
Real-time replication: every audit event is POSTed to your endpoint with an HMAC-SHA256 signature in X-Audit-Signature. Retries with exponential backoff (1, 5, 15, 60, 300 seconds). Webhook auto-disables after 10 consecutive delivery failures and emails the tenant on disable.
POST/audit/v1/webhooks
Body:
| Field | Type | Notes |
|---|---|---|
target_url | string | Your endpoint, e.g. https://my-app.com/audit-events |
event_type_filter | array<string> | Which events to forward. ["*"] = all events. |
Returns 201 with webhook_id + secret. The secret is shown ONCE — save it. Returns 402 for non-Scale tiers.
{
"webhook_id": "8be51edc-e796-47f4-bc5a-304886d87830",
"secret": "whsec_audit_xxxxxxxxxxxx",
"target_url": "https://my-app.com/audit-events",
"event_type_filter": ["*"],
"enabled": true,
"created_at": "2026-05-10T01:54:50Z"
}
GET/audit/v1/webhooks
List your configured webhooks. Shows enabled/disabled state, last delivery, consecutive failure count.
DELETE/audit/v1/webhooks/{id}
Remove a webhook.
POST/audit/v1/webhooks/{id}/test
Queue a synthetic test delivery to verify your endpoint. Inspect result via /deliveries.
GET/audit/v1/webhooks/{id}/deliveries
Returns most-recent delivery attempts with HTTP status, response excerpt, and timestamps. Useful for debugging.
Delivery payload
Body is canonical JSON (sorted keys, no whitespace). Includes entry_hash so you can verify chain position on your side.
POST https://my-app.com/audit-events
Content-Type: application/json
X-Audit-Signature: 99b5170a37a5a23f453a9bab9b415736efdaaf45a7dfd6791521dc221adab148
User-Agent: EAS-Audit-Lite-Webhook/1.0
{"chain_position":1,"entry_hash":"093a7b...","event_id":6,"event_type":"order.placed","payload":{"amount":4999,"order_id":"o1"},"tenant_id":"al_xxx","ts_ms":1778464494469}
Verify the HMAC signature
Python:
import hmac, hashlib
SECRET = "whsec_audit_xxxxxxxxxxxx" # stored from creation response
def verify(body_bytes: bytes, signature_header: str) -> bool:
expected = hmac.new(SECRET.encode(), body_bytes, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header)
# In FastAPI / Flask:
sig = request.headers.get("X-Audit-Signature", "")
body = request.body() # raw bytes
if not verify(body, sig):
return Response(status=401)
Node.js:
const crypto = require("crypto");
const SECRET = "whsec_audit_xxxxxxxxxxxx";
function verify(bodyBuffer, signatureHeader) {
const expected = crypto.createHmac("sha256", SECRET)
.update(bodyBuffer)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(signatureHeader, "hex"),
);
}
// Express:
app.post("/audit-events", express.raw({type: "application/json"}), (req, res) => {
const sig = req.headers["x-audit-signature"];
if (!verify(req.body, sig)) return res.status(401).end();
// safe to parse and use
const event = JSON.parse(req.body.toString());
// ...
});
Custom retention (Scale)
Choose how long EAS Audit Lite stores your events. A daily cron deletes events older than your retention_days setting. Pruned events cannot be recovered — export first if you need them long-term.
| Tier | Retention |
|---|---|
| Starter | 30 days (fixed) |
| Growth | 90 days (fixed) |
| Scale | Configurable: 30 / 90 / 180 / 365 / forever (36500 days) |
PATCH/audit/v1/account/retention
curl -X PATCH 'https://audit.eliteagenticsolutions.com/audit/v1/account/retention' \
-H "Authorization: Bearer empire_audit_xxx" \
-H "Content-Type: application/json" \
-d '{"retention_days": 365}'
Returns 400 if the value is not in your tier's allowed list, 402 if you're not on Scale.
GET/audit/v1/account/retention
Returns your current config + the allowed options for your tier.
{
"tenant_id": "al_xxx",
"tier": "scale",
"retention_days_configured": 180,
"retention_days_effective": 180,
"options_available_for_tier": [30, 90, 180, 365, 36500]
}
Query the age of stored events
Use the existing /v1/events?from=<ts_ms> endpoint with the timestamp from NOW - retention_days. Compute on your side:
# Python
import time
from_ms = int((time.time() - 180*86400) * 1000)
url = f"https://audit.eliteagenticsolutions.com/audit/v1/events?from={from_ms}"
Chain integrity after pruning
When old events are deleted, the lowest remaining event is re-anchored to your tenant's GENESIS hash and its entry_hash is recomputed. The chain stays self-consistent for /v1/chain/verify. Cryptographic links to deleted events are broken — export the chain regularly if you need historical proof.
Error codes
| Code | Meaning |
|---|---|
| 400 | Malformed request body or value not allowed |
| 401 | Missing / invalid Bearer token |
| 402 | Tier upgrade required (Growth or Scale feature) |
| 403 | Admin endpoint accessed without admin token |
| 404 | Event ID / webhook not found in your tenant |
| 429 | Rate limit or monthly quota exceeded |
Code samples
Python
import requests
r = requests.post(
"https://audit.eliteagenticsolutions.com/audit/v1/events",
headers={"Authorization": "Bearer empire_audit_xxx"},
json={"event_type": "user.login", "payload": {"user_id": "u123"}},
)
print(r.json()) # {"event_id": ..., "chain_position": ..., ...}
Node.js
const res = await fetch("https://audit.eliteagenticsolutions.com/audit/v1/events", {
method: "POST",
headers: {
"Authorization": "Bearer empire_audit_xxx",
"Content-Type": "application/json",
},
body: JSON.stringify({event_type: "user.login", payload: {user_id: "u123"}}),
});
console.log(await res.json());
Go
body, _ := json.Marshal(map[string]interface{}{
"event_type": "user.login",
"payload": map[string]string{"user_id": "u123"},
})
req, _ := http.NewRequest("POST", "https://audit.eliteagenticsolutions.com/audit/v1/events", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer empire_audit_xxx")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
Hash chain concepts
Every event you write computes:
payload_hash = sha256(canonical_json(payload))
entry_hash = sha256(tenant_id | prev_hash | event_type | ts_ms | payload_hash)
The very first event uses prev_hash = sha256("GENESIS::" + tenant_id). Every subsequent event's prev_hash is the previous event's entry_hash.
To tamper undetectably, an attacker would need to recompute every hash after their edit — but they cannot rewrite hashes they have already exported, anchored, or shown to auditors. Tampering breaks loud.
Canonical JSON: keys sorted, no whitespace. This guarantees the same payload always hashes to the same value regardless of key order.