Getting Started
Quick Start — 3 Steps
- 1Copy your Tenant ID and HMAC Secret from Settings
- 2Send a
POST /v1/decisionsrequest with your fraud decision data - 3See your record appear in Event Search — it's now cryptographically sealed in your ledger
API Endpoint & Authentication
https://api.attestr.ioPOST /v1/decisions| Header | Value |
|---|---|
| Content-Type | application/json |
| X-Tenant-Id | Your tenant UUID |
| X-Timestamp | ISO-8601 UTC (within 5 min of server time) |
| X-Signature | HMAC-SHA256 hex digest |
Concatenate the method, path, timestamp, and body with newlines, then HMAC-SHA256 with your secret:
message = "POST\n/v1/decisions\n{timestamp}\n{body}"
signature = HMAC-SHA256(hmac_secret, message)Request Payload
Maximum body size: 1 MB. No additional properties allowed.
| Field | Type | Req. | Constraints |
|---|---|---|---|
| event_id | string | Yes | 1–255 chars. Unique per tenant (idempotent key) |
| decision | string | Yes | 1–50 chars (e.g. ALLOW, BLOCK, REVIEW) |
| reason_codes | string[] | Yes | 0–100 items |
| decided_at | string | Yes | ISO-8601 datetime |
| score | number | No | 0.0–1.0 (risk/confidence score) |
| model_version | string | No | Max 100 chars |
| policy_version | string | No | Max 100 chars |
| feature_contributions | object | No | { string: number } — per-feature weights |
| metadata | object | No | Max 50 properties. Arbitrary JSON |
| input_hash | string | No | Exactly 64 lowercase hex chars (SHA-256 of raw input) |
Code Examples
Replace the placeholder credentials with your own from Settings.
TENANT_ID="demo-tenant-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
HMAC_SECRET="<your-hmac-secret>" # from Settings → HMAC Secret
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%S.000Z)
BODY='{"event_id":"txn_001","decision":"ALLOW","score":0.12,"reason_codes":["trusted_device"],"decided_at":"'$TIMESTAMP'"}'
SIGNATURE=$(printf "POST\n/v1/decisions\n%s\n%s" "$TIMESTAMP" "$BODY" | \
openssl dgst -sha256 -hmac "$HMAC_SECRET" -hex | awk '{print $2}')
curl -X POST https://api.attestr.io/v1/decisions \
-H "Content-Type: application/json" \
-H "X-Tenant-Id: $TENANT_ID" \
-H "X-Timestamp: $TIMESTAMP" \
-H "X-Signature: $SIGNATURE" \
-d "$BODY"import crypto from 'crypto';
const TENANT_ID = 'demo-tenant-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
const HMAC_SECRET = '<your-hmac-secret>'; // from Settings → HMAC Secret
const API_URL = 'https://api.attestr.io/v1/decisions';
const body = JSON.stringify({
event_id: 'txn_001',
decision: 'ALLOW',
score: 0.12,
reason_codes: ['trusted_device'],
decided_at: new Date().toISOString(),
});
const timestamp = new Date().toISOString();
const message = `POST\n/v1/decisions\n${timestamp}\n${body}`;
const signature = crypto
.createHmac('sha256', HMAC_SECRET)
.update(message)
.digest('hex');
const res = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Tenant-Id': TENANT_ID,
'X-Timestamp': timestamp,
'X-Signature': signature,
},
body,
});
console.log(await res.json());import hmac, hashlib, json, requests
from datetime import datetime, timezone
TENANT_ID = 'demo-tenant-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
HMAC_SECRET = '<your-hmac-secret>' # from Settings → HMAC Secret
API_URL = 'https://api.attestr.io/v1/decisions'
body = json.dumps({
'event_id': 'txn_001',
'decision': 'ALLOW',
'score': 0.12,
'reason_codes': ['trusted_device'],
'decided_at': datetime.now(timezone.utc).isoformat(),
})
timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.000Z')
message = f'POST\n/v1/decisions\n{timestamp}\n{body}'
signature = hmac.new(
HMAC_SECRET.encode(), message.encode(), hashlib.sha256
).hexdigest()
res = requests.post(API_URL,
headers={
'Content-Type': 'application/json',
'X-Tenant-Id': TENANT_ID,
'X-Timestamp': timestamp,
'X-Signature': signature,
},
data=body,
)
print(res.json())Response Format
{
"ledger_entry_id": "a1b2c3d4-e5f6-...",
"sequence_number": 42,
"record_hash": "sha256:6ab29f...",
"previous_hash": "sha256:f8c3e1...",
"platform_signature": "ed25519:D0jm...",
"ingested_at": "2026-03-10T14:30:01Z"
}If you send the same event_id again, the API returns the existing record with a 200 status. No duplicate is created.
Rate Limits
| Tier | Req/min | Records/mo |
|---|---|---|
| Free | 10 | 500 |
| Starter (demo) | 100 | 10,000 |
| Pro | 1,000 | 100,000 |
| Enterprise | 10,000 | Custom |
Check RateLimit-Remaining and RateLimit-Reset response headers.
Status Codes
| Code | Meaning |
|---|---|
| 200 | Idempotent replay — event already recorded |
| 201 | Decision recorded successfully |
| 400 | Invalid payload — see detail field |
| 401 | Authentication failed (bad signature or expired timestamp) |
| 413 | Request body exceeds 1 MB |
| 429 | Rate limit exceeded — retry after delay |
Team Access
Invite compliance officers, auditors, or teammates to view your ledger. Auditors get read-only access to events, evidence exports, ledger health, and anomaly alerts — they cannot modify settings or manage API credentials.
Invite auditors in Settings →Every ledger record is signed with Ed25519. Fetch the public key via GET /v1/public-key (no authentication required) to verify signatures independently.