DEMO MODE — Exploring with sample dataStart free →
Acme FinancialStarter
admin@acmefinancial.com

Getting Started

Quick Start — 3 Steps

  1. 1Copy your Tenant ID and HMAC Secret from Settings
  2. 2Send a POST /v1/decisions request with your fraud decision data
  3. 3See your record appear in Event Search — it's now cryptographically sealed in your ledger

API Endpoint & Authentication

Base URL
https://api.attestr.io
Endpoint
POST /v1/decisions
Required Headers
HeaderValue
Content-Typeapplication/json
X-Tenant-IdYour tenant UUID
X-TimestampISO-8601 UTC (within 5 min of server time)
X-SignatureHMAC-SHA256 hex digest
Signature Computation

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.

FieldTypeReq.Constraints
event_idstringYes1–255 chars. Unique per tenant (idempotent key)
decisionstringYes1–50 chars (e.g. ALLOW, BLOCK, REVIEW)
reason_codesstring[]Yes0–100 items
decided_atstringYesISO-8601 datetime
scorenumberNo0.0–1.0 (risk/confidence score)
model_versionstringNoMax 100 chars
policy_versionstringNoMax 100 chars
feature_contributionsobjectNo{ string: number } — per-feature weights
metadataobjectNoMax 50 properties. Arbitrary JSON
input_hashstringNoExactly 64 lowercase hex chars (SHA-256 of raw input)

Code Examples

Replace the placeholder credentials with your own from Settings.

bash (curl)
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"
node.js
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());
python
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

201 CreatedNew record ingested
json
{
  "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"
}
200 OKIdempotent replay (event_id exists)

If you send the same event_id again, the API returns the existing record with a 200 status. No duplicate is created.

Rate Limits

TierReq/minRecords/mo
Free10500
Starter (demo)10010,000
Pro1,000100,000
Enterprise10,000Custom

Check RateLimit-Remaining and RateLimit-Reset response headers.

Status Codes

CodeMeaning
200Idempotent replay — event already recorded
201Decision recorded successfully
400Invalid payload — see detail field
401Authentication failed (bad signature or expired timestamp)
413Request body exceeds 1 MB
429Rate 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 →
Verify Signatures Offline

Every ledger record is signed with Ed25519. Fetch the public key via GET /v1/public-key (no authentication required) to verify signatures independently.