AventraGuard Public API
A server-to-server REST API for ingesting transactions and parties, receiving real-time AML risk signals, and reading compliance artifacts — all secured with HMAC-SHA256 signed requests.
Base URLs
There is no separate sandbox host — Staging is the sandbox. Use it with a ak_test_ key to integrate and test against real platform behaviour without touching production data or filing to FINTRAC.
| Environment | Base URL | Key prefix |
|---|---|---|
| Production | https://api.aventraguard.com | ak_live_ |
| Staging (sandbox) | https://staging.aventraguard.com | ak_test_ |
| Dev | https://dev.aventraguard.com | ak_test_ |
The key prefix and the host must agree — a ak_live_ key on the staging/dev (test) hosts is rejected with HTTP 401, and a ak_test_ key on production is rejected too.
Step 1 — Get credentials
An API key is an opaque string that identifies your reporting entity and grants a set of permissions (scopes). It is issued by an AventraGuard operator — there is no public self-service sign-up.
A webhook secret is a separate credential used only to verify that events we push to your endpoint came from AventraGuard (not an attacker who found your URL).
- 1Ask the admin or MLRO on your team to sign in to AventraGuard → Settings → Public API → Keys → Create key. Choose a label (e.g.
acme-prod-2026), select scopes, set mode to Live for production or Test for the staging/dev (sandbox) hosts. - 2The raw key is shown once in a modal. Copy it directly into your secrets manager (AWS Secrets Manager, HashiCorp Vault, GitHub Actions secret, etc.). Once the modal closes, only the hashed form is stored — there is no recovery path.
- 3To create a webhook signing secret, go to Settings → Public API → Webhooks → Add webhook. The
secretfield in the 201 response (or the UI modal) is also shown exactly once. Store it the same way.
Step 2 — Make your first signed request
HMAC (Hash-based Message Authentication Code) is a way of proving you sent a specific request without a password travelling on the wire. You compute a cryptographic fingerprint of the request using your API key as the secret, then send the fingerprint in a header. The server recomputes the fingerprint and compares. If they match, you must have the key.
GET requests only require the X-API-Key header. POST/PATCH/DELETE additionally require X-Aventra-Timestamp and X-Aventra-Signature — details in Authentication & Signing. Here is a GET to get you started:
Response envelope
Every list endpoint uses the same envelope. A successful call with no data returns {"items":[],"total":0,"limit":5,"next_cursor":""} — expected on a fresh key before you push any data.
| Field | Type | Meaning |
|---|---|---|
items | array | The page of results. |
total | integer | Total rows matching your filter across all pages. |
limit | integer | Page size used (1–200, default 50). |
next_cursor | string | Opaque cursor; empty on the last page. Pass as ?cursor=… on the next request. |
Step 3 — Receive your first webhook
A webhook is an HTTP POST that AventraGuard sends to a URL you control whenever something happens — an alert fires, a case opens, a filing is submitted. Instead of polling GET /v1/alerts every few seconds, you register once and we push events to you.
- 1Expose a public HTTPS URL (e.g.
https://hooks.acme.com/aventraguard). It must resolve to a public IP — loopback and RFC 1918 addresses are rejected. - 2Register it:
POST /v1/webhookswith{"url":"https://hooks.acme.com/aventraguard","label":"acme-prod"}. Copy thesecretfrom the 201 response immediately. - 3In your handler, read the raw request body bytes before any JSON parsing, then verify the
X-Aventra-Webhook-Signatureheader. Return HTTP 200 within 15 seconds (offload slow work to a background queue). See Signature verification.
API Keys
An API key identifies your reporting entity and carries a set of scopes. Every request must include it in the X-API-Key header.
Key format
| Mode | Prefix | Example |
|---|---|---|
| Live (production) | ak_live_ | ak_live_3f8a9e2b…8e9f |
| Test (staging/dev sandbox) | ak_test_ | ak_test_3f8a9e2b…8e9f |
The body is 64 lowercase hex characters (32 bytes of entropy). Total length: 72 characters. We store only the SHA-256 hash — even an operator with full database access cannot recover the raw key.
Scopes
| Scope | Grants |
|---|---|
parties:read | List parties, get a single party, list a party's screening hits. |
parties:write | Create/upsert a party, trigger tier recompute. |
transactions:write | Ingest a transaction (single or batch). |
transactions:read | Read back ingested transactions. |
alerts:read | List alerts, get a single alert. |
alerts:write | Disposition an alert (true_positive / false_positive). |
cases:read | List cases, get a single case. |
webhooks:read | List webhook registrations. |
webhooks:write | Create / delete webhook registrations. |
filings:read | List STR filings, get a single filing. |
audit:read | Read audit log entries. Granted on request. |
Request the smallest set you need. A missing scope returns 403 insufficient-scope with the required scope name in extensions.required_scope.
Request Signing (HMAC-SHA256)
Signing proves three things at once: you possess the secret, the request was not modified in transit, and (combined with the timestamp) it is not a replay of an old request.
When signing is required
| Method | Signing |
|---|---|
GET | Optional — you may include it for defence in depth; the server does not require it. |
POST, PATCH, DELETE | Required. |
The three headers
| Header | Value |
|---|---|
X-API-Key | Your raw API key (ak_live_…). |
X-Aventra-Timestamp | Current Unix time in seconds as a decimal string (e.g. 1748534400). Must be within 5 minutes of the server clock. |
X-Aventra-Signature | 64 lowercase hex chars: hex(HMAC-SHA256(raw_api_key, string_to_sign)). |
String to sign
Construct exactly this string with literal newline (\n) separators. No trailing newline.
| Field | Description |
|---|---|
METHOD | Uppercase HTTP method: GET, POST, PATCH, DELETE. |
PATH_WITH_QUERY | Path + query string exactly as on the wire: /v1/parties?limit=5. No scheme/host. Do not re-encode. |
TIMESTAMP | Same decimal string as X-Aventra-Timestamp, byte-for-byte. |
BODY_SHA256_HEX | Lowercase hex SHA-256 of the raw request body bytes. For an empty body: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855. |
Worked Example
Step-by-step signing of POST /v1/parties with a minimal body. The values below are real — you can verify them against the published test vectors.
| Step | Value |
|---|---|
| API key | ak_test_3f8a9e2b1c0d4e5f6a7b8c9d0e1f2a3b |
| Method | POST |
| Path | /v1/parties |
| Timestamp | 1748534400 |
| Body | {"source_party_id":"acme-cust-00001","party_type":"individual"} |
| Body SHA-256 | a5394d35181f07466c1e5f70baa220d07a8f2548613422409c7761a05a593d97 |
| String to sign | POST\n/v1/parties\n1748534400\na5394d35…3d97 |
| Signature | 6e0652be97da59f51cfce47f7ef84b2c1ad2923145f04dadd836aaff33819f4f |
Test Vectors
Five precomputed vectors are published at docs/api/test-vectors/hmac-signing-v1.json. Run your signing implementation against these before sending any real request — the server's own test suite regenerates them on every run. Here is the post_party vector:
| Field | Value |
|---|---|
| Key | ak_test_3f8a9e2b1c0d4e5f6a7b8c9d0e1f2a3b |
| Method | POST |
| Path | /v1/parties |
| Timestamp | 1748534400 |
| Body SHA-256 | a5394d35181f07466c1e5f70baa220d07a8f2548613422409c7761a05a593d97 |
| Expected signature | 6e0652be97da59f51cfce47f7ef84b2c1ad2923145f04dadd836aaff33819f4f |
Common drift causes: lowercase method, including the Host: header in the string, stripping the query string, uppercase hex output.
Idempotency
Idempotency means "doing the same thing twice has the same effect as doing it once." The Idempotency-Key header lets you safely retry a POST or PATCH after a network failure without creating duplicates.
- A UUID v4 is recommended (any unique string up to 128 bytes is accepted).
- Keys are scoped per API key — your key's
acme-001does not collide with another integrator's. - The first successful 2xx response is cached for 24 hours. Retries with the same key replay the cached response and add
Idempotency-Replayed: true. - Only 2xx responses are cached. A validation error on the first try will not be replayed — the next attempt runs fresh.
- Reusing the same key with a different request body returns 409
idempotency-conflict. Generate a fresh UUID per logical operation. - Cap your retry horizon below 24 hours for write endpoints — a retry after key expiry will re-execute.
Replay Protection
Two mechanisms prevent replayed requests:
- Timestamp window:
X-Aventra-Timestampmust be within 5 minutes of the server clock. Drift returns 401timestamp-expired. Keep your servers on NTP; always compute the timestamp at request-build time, not at config load time. - Nonce cache: The server derives
nonce = sha256(timestamp + ":" + signature)and stores it for 10 minutes. A repeated (timestamp, signature) pair returns 401replay-detected. Always re-sign on retry with a fresh timestamp.
Rate Limits
| Operation | Limit |
|---|---|
| Read (GET) | 600 requests / minute per key |
| Write (POST / PATCH / DELETE) | 60 requests / minute per key |
On 429 the server returns a Retry-After header. Sleep that many seconds, then re-sign and retry with the same Idempotency-Key on mutations. Response headers RateLimit-Limit, RateLimit-Remaining, and RateLimit-Reset are available on every response to let you pace proactively.
API Reference
Every endpoint, parameter, and response field. Signing column: required = POST/PATCH/DELETE; optional = GET.
/v1 endpoints require. To call /v1 for real, use the signed examples and SDKs on this page.
Parties
A party is a customer or business entity subject to AML monitoring. Parties are the unit of risk scoring, sanctions screening, and EDD tracking.
Create / upsert a party
Insert a customer record, or update it if (re_id, "public_api", source_party_id) already exists. Scope: parties:write. Returns 201.
| Field | Type | Req | Description |
|---|---|---|---|
source_party_id | string | yes | Your stable customer ID. Forms the upsert key. |
party_type | string | yes | individual or business. |
given_name | string | no | Individual given (first) name. |
family_name | string | no | Individual family (last) name. |
legal_name | string | no | Full legal name for individuals. |
business_name | string | no | Registered business name. |
email | string | no | Contact email. |
phone | string | no | E.164 format. |
address_country | string | no | ISO 3166-1 alpha-2 (e.g. CA). |
address_region | string | no | Province / state. |
address_city | string | no | City. |
address_postal | string | no | Postal / ZIP. |
birth_date | string | no | ISO 8601 YYYY-MM-DD. Individuals only. Stored encrypted, never returned. |
kyc_status | string | no | Free-form KYC status from your vendor. |
source_created_at | string | no | RFC 3339 — when your system created the record. |
government_id_number | string | no | Stored AES-256-GCM encrypted; never returned. Individuals only. |
Response 201:
| Status | When |
|---|---|
| 201 | Created or upserted. |
| 400 | Body not JSON. |
| 422 | Validation failed (unknown party_type, bad date format, etc.). Read extensions.fields[]. |
| 503 | DB write failed (transient). Retry with same Idempotency-Key. |
List parties
Scope: parties:read. Query params: limit (1–200, default 50), cursor, order (asc|desc).
Get a party
Scope: parties:read. {id} is the AventraGuard numeric party ID (not your source_party_id). Returns 404 if the party does not exist or belongs to a different RE.
Recompute risk tier
Scope: parties:write. Triggers an immediate re-evaluation of the party's risk tier. Body is ignored. Returns 200 with {"party_id":10421,"previous_tier":"T2","current_tier":"T3","action":"upgraded","change_id":88421}. Action values: unchanged, upgraded, downgrade_pending, downgraded.
Party screening hits
Scope: parties:read. Returns sanctions, PEP, and adverse-media hits attached to this party, with full match detail.
entry_name, matched_name, screened_name, subject_name, and disposed_by are sensitive. If you persist these fields in your own store or console, strip them at the transform layer. Do not surface sanctioned/matched names in integrator UIs.
Key field notes:
list_source— the screening list identifier (e.g.OSFI). Notlist_code.disposition—pending|confirmed|cleared. Notstatus.first_seen_at/last_seen_at— the timestamps. There is nocreated_aton hits.
Transactions
Push transactions for AML rule evaluation. The 202 response is immediate — screening runs asynchronously.
Ingest a transaction
Scope: transactions:write. Idempotent with Idempotency-Key.
| Field | Type | Req | Description |
|---|---|---|---|
source_transaction_id | string | yes | Your stable transaction ID (upsert key). |
source_party_id | string | yes | The party who initiated. May arrive before the party is pushed — will auto-link when party is created. |
amount | string | yes | Decimal as string ("123.45") to preserve precision. |
currency | string | yes | ISO 4217 (CAD, USD, EUR). |
status | string | yes | Your status code (e.g. completed). |
method | string | yes | Payment method (e.g. e_transfer, wire). |
action | string | yes | sale | deposit | withdrawal | transfer | payout | refund | reserve | complete | cancel | void | verification | request | bill_payment | other. |
direction | string | no | credit | debit | neutral. Overrides action-derived heuristic when your source has authoritative direction. |
occurred_at | string | yes | RFC 3339 — when the transaction happened. |
source_created_at | string | yes | RFC 3339 — when your system created it. |
fee | string | no | Same decimal-string format as amount. |
Response 202: {"id":"0193b6a8-…","source_transaction_id":"acme-tx-001","status":"accepted","screening_queued":true}. Listen on the alert.created webhook (or poll GET /v1/alerts) for alerts produced by this transaction.
List / get transactions
Scope: transactions:read. List supports limit, cursor, and optional source_party_id filter. party_linked: false indicates an orphan transaction (party not yet ingested) — it will link automatically when the party arrives.
Screening Hits
List all screening hits
Scope: parties:read. RE-scoped, newest-first, offset-paginated — for reconciliation after downtime without iterating parties one-by-one. Supports ?since=<RFC3339> for incremental sync. Deliberately omits matched/screened names (tipping-off sensitive) — use GET /v1/parties/{id}/screening-hits for full per-party detail.
Key fields: hit_id, party_id, list_source, hit_kind, disposition (pending|confirmed|cleared), first_seen_at, last_seen_at.
Alerts
An alert is an AML risk signal produced by the rule engine, sanctions screening, PEP match, or behavioural model.
List / get alerts
Scope: alerts:read. List params: limit, cursor, order. Fields: id, alert_type, severity, status (open|escalated|dismissed), rule_code, detail, party_id, amount, currency.
Disposition an alert
Scope: alerts:write. Body: {"disposition":"true_positive"} or {"disposition":"false_positive","notes":"..."}. true_positive → escalated; false_positive → dismissed.
Cases
A case is the unit of analyst investigation, opened when an alert is escalated. Read-only in Phase 1.
List / get cases
Scope: cases:read. Key fields include alert_count (number of alerts linked to the case), status, opened_at, due_at, overdue. On a closed case, closed_at is populated.
Filings
STR (Suspicious Transaction Report) filings are read-only via the API. The submission workflow remains in-platform because FINTRAC submission requires MLRO four-eyes review.
List / get filings
Scope: filings:read. Status values: draft, pending_approval, approved, submitted, returned. The fintrac_receipt_id field is set on submission — use this for your retention obligations.
Batches
Batch management endpoints — see also Bulk / Batch Ingest for the full workflow.
Batch status / errors / cancel
Scope: batch's resource read scope (transactions:read for transaction batches). state: accepted → processing → completed | cancelled. Cancel requires write scope; idempotent for already-cancelled batches; 409 if already completed.
Webhooks (management)
Register a webhook
Scope: webhooks:write. Body: url (required, HTTPS public), label (optional), event_filters (optional array — empty/omitted = all events). The 201 response contains secret exactly once — store it immediately in your secrets manager.
| Status | When |
|---|---|
| 201 | Created. Capture secret. |
| 400 | url missing or body not JSON. |
| 422 | URL validation failed (not HTTPS, private/loopback host, credentials in URL). |
List / delete webhooks
List scope: webhooks:read. Delete scope: webhooks:write. Delete returns 204. In-flight deliveries already enqueued will still complete their retry schedule; no new events will be queued after revocation.
Audit Log
audit:read scope is not assigned by default. Contact contact@aventraguard.com to request access. The audit log records every state-changing operation across all resources with the actor identity, timestamp, and before/after state.
Bulk / Batch Ingest
Upload thousands of transactions or parties in a single request for historical backfills or high-volume onboarding.
What is NDJSON?
NDJSON (Newline-Delimited JSON) is simply one complete JSON object per line, lines separated by \n. There is no outer array and no trailing newline. Each line is exactly the same shape as the single-row POST body.
- Maximum body size: 4 MiB per request. Split larger backfills across multiple batches.
- Invalid rows do not abort the batch — they are recorded as failed with a reason while valid rows proceed.
- Blank lines are ignored.
- Content-Type must be
application/x-ndjson.
Async 202 flow
The server validates each line, enqueues valid rows for asynchronous processing, and returns 202 immediately with a batch_id. A 202 is not confirmation every row landed.
202 response:
Tracking status
Poll GET /v1/batches/{id} or subscribe to the batch.completed webhook. State progresses: accepted → processing → completed.
Cancelling a batch
Stop processing the unprocessed remainder of a batch — e.g. a backfill submitted in error. Already-ingested rows stay ingested. Idempotent for already-cancelled batches. Returns 409 if the batch is already completed (its work is done).
For parties, use POST /v1/parties/batch / client.IngestPartiesBatch / client.ingestPartiesBatch — identical shape and flow.
Webhooks
AventraGuard pushes events to your HTTPS endpoint the moment they happen — no polling required.
What is a webhook?
A webhook is an HTTP POST we send to a URL you control whenever something noteworthy happens — an alert fires, a case opens, a screening hit is confirmed. You give us a URL; we call it with a JSON payload. You return HTTP 200 to acknowledge, and we move on. If you return anything else, we retry on an exponential curve.
The key difference from polling: instead of asking "did anything happen?" every few seconds, you register once and we tell you immediately. This reduces latency and API load for both sides.
Event catalogue
| Event type | When it fires | Key use case |
|---|---|---|
alert.created | A new AML alert is raised. | Open a hold, notify an analyst. |
alert.disposed | An alert is dispositioned (confirmed or dismissed). | Release a hold; sync local alert state. |
case.opened | A case is opened for investigation. | Surface to customer-service; track SLA. |
case.closed | A case is closed by analyst or MLRO. | Restore account access or escalate. |
filing.submitted | An STR is submitted to FINTRAC and a receipt ID is available. | Archive receipt for retention obligations. |
party.screening_hit.created | A new sanctions/PEP hit is detected (pre-review). | Place a provisional hold immediately. |
party.screening_hit | A screening hit is confirmed by an analyst. | Escalate provisional hold to firm hold. |
party.risk_tier_changed | A party's risk tier (T1–T5) changes. | Update transaction-limit rules. |
batch.completed | A bulk-ingest batch finishes (terminal). Not fired on cancel. | Avoid polling; reconcile counts. |
Payload envelope
event_id is a UUID v7 that is stable across all retry attempts — use it as your idempotency key in your handler. created_at is the event creation time, not the delivery time.
Signature verification
You must verify every webhook before acting on its contents. An attacker who finds your URL could otherwise replay forged events.
The signature header
t— Unix timestamp (seconds) of the event creation time. Stable across retries.v1—hex(HMAC-SHA256(raw_secret, "<t>.<raw_body_bytes>")). The signed message binds the timestamp to the body.
During secret rotation the header carries two v1= values. Accept the delivery if any matches your stored secret.
Step-by-step verification
- Read the raw request body bytes before any JSON parsing. Re-serialising the parsed JSON breaks the hash.
- Parse
X-Aventra-Webhook-Signature: split on,, extractt=(integer) and allv1=values. - Build the signed message:
signed_msg = "<t>" + "." + raw_body_bytes. - Compute
expected = hex(HMAC-SHA256(raw_secret_utf8, signed_msg)). - Compare
expectedto eachv1=value in constant time. Accept if any matches. - Optionally reject if
abs(now - t) > 300(5-minute clock skew tolerance). - Use
event_idas your idempotency key — deduplicate before processing.
Secret rotation
Zero-downtime rotation replaces the signing secret without dropping deliveries. During the overlap window (default 14 days) AventraGuard signs every delivery with both secrets:
Your verification code already handles this if you iterate all v1= values. No code change needed — just update your stored secret and the overlap window covers the rest.
Endpoints: POST /api/admin/public-api/webhooks/{id}/rotate-secret and POST /api/admin/public-api/webhooks/{id}/finalize-secret-rotation. Both require admin/super_user + TOTP.
Retries & dead-letter
AventraGuard retries a failed delivery 6 more times (7 total attempts) on a graduated curve:
| Attempt | Nominal delay | Range (±20% jitter) |
|---|---|---|
| 1 (initial) | Immediate | — |
| 2 | 1 min | 48 s – 1 m 12 s |
| 3 | 5 min | 4 m – 6 m |
| 4 | 30 min | 24 m – 36 m |
| 5 | 2 h | 1 h 36 m – 2 h 24 m |
| 6 | 8 h | 6 h 24 m – 9 h 36 m |
| 7 | 24 h | 19 h 12 m – 28 h 48 m |
After all 7 attempts fail the delivery is marked dead_lettered. The operator can manually re-trigger from the admin UI. Per-delivery timeout: 15 seconds. HTTP 4xx permanent failures (400, 401, 403, 404, 405, 410, 415, 422) skip the retry curve and go straight to dead-letter.
Each delivery includes X-AM-Delivery-Attempt: <n> (1-indexed) for logging.
Tipping-off / PII — PCMLTFA s.66
party.screening_hit.created and party.screening_hit webhook events are deliberately trigger-only. They carry hit_id, re_id, hit_kind, and party_id — but no matched name, list source, or score. This is intentional: disclosing screening matches to the subject is a criminal offence under PCMLTFA s.66. Fetch full detail via GET /v1/parties/{party_id}/screening-hits within your own access-controlled system. Fields to strip before persisting to external stores: entry_name, matched_name, screened_name, subject_name, disposed_by, return_reason.
Receiver checklist
- Receiver is HTTPS and resolves to a public IP.
- Verifies
X-Aventra-Webhook-Signatureon every request; returns 401 on mismatch. - Deduplicates by
event_id(idempotent handler). - Returns 200 within 15 seconds; defers slow work to a background queue.
- Returns 5xx on transient failures (so retries help); 4xx only on permanent errors.
- Logs
event_id,event_type,X-AM-Delivery-Attempt, and processing status. - Webhook secret is stored in a secrets manager, not source control.
- Error responses do not echo back request fields — avoids PII in the delivery log.
Errors
Every error from the AventraGuard API follows RFC 7807 (Problem Details for HTTP APIs) so you only need one parser for your whole integration.
Error shape
Switch on type, not title or detail — type is part of the contract; title/detail are wording we may refine. The type URI last segment is the stable code string.
All error codes
| HTTP | Type code | When | Action |
|---|---|---|---|
| 400 | invalid-request | Body not JSON, URL param not an integer, alert already closed. | Fix the request — will not succeed as-is. |
| 401 | missing-api-key | No X-API-Key header. | Add the header on every request. |
| 401 | invalid-api-key | Key not found or revoked. Same code for both by design. | Check your secrets manager and admin UI. |
| 401 | signature-mismatch | HMAC did not match. | Verify your signed-string format and that you are using raw body bytes. |
| 401 | timestamp-expired | Timestamp outside 5-minute window. | Check NTP sync; compute timestamp at request-build time. |
| 401 | replay-detected | Same (timestamp, signature) within 10 minutes. | Re-sign with a fresh timestamp on every retry. |
| 403 | insufficient-scope | Key lacks the required scope. | Check extensions.required_scope; ask operator to add scope. |
| 404 | not-found | Resource does not exist or belongs to another RE. | Confirm the ID and the key. |
| 405 | method-not-allowed | Method not supported. Check Allow header. | Use a method listed in Allow. |
| 409 | idempotency-conflict | Same Idempotency-Key reused with a different body. | Generate a fresh UUID for each logical operation. |
| 422 | validation-error | Field validation failed. | Read extensions.fields[] and fix the input. |
| 429 | rate-limit-exceeded | Per-key rate limit hit. | Sleep Retry-After seconds, re-sign, retry. |
| 500 | internal-error | Unexpected server failure. | Retry with capped exponential backoff; email contact@aventraguard.com if persistent. |
| 503 | service-unavailable | Transient downstream failure (DB, screening queue). | Retry with exponential backoff; keep Idempotency-Key stable. |
409 Idempotency conflict
You reused an Idempotency-Key for a logically different operation. The first response was cached and replaying it would be wrong for the new body. Fix: generate a fresh UUID per logical operation. The pattern aventra:tx:<your-tx-id> (deterministic from your source ID) is safe and convenient.
429 Rate limit & Retry-After
Go SDK
Official Go client — stdlib only, zero third-party dependencies, concurrent-safe.
Install
Requires Go 1.25+.
Quick start
- 1Import the package as
aventra. - 2Call
aventra.New(baseURL, apiKey, opts...). The API key is the HMAC signing key — no separate signing secret. - 3Call methods on the returned
*Client. Every method accepts acontext.Contextas first argument. - 4For write methods, supply
aventra.WithIdempotencyKey("your-id")to enable safe retries and server-side deduplication.
All methods
| Method | Endpoint | Notes |
|---|---|---|
CreateParty(ctx, req, opts...) | POST /v1/parties | Returns PartyResponse. |
ListParties(ctx, params) | GET /v1/parties | Returns PartyListResponse. |
GetParty(ctx, id) | GET /v1/parties/{id} | |
GetPartyScreeningHits(ctx, partyID, params) | GET /v1/parties/{id}/screening-hits | |
IngestTransaction(ctx, req, opts...) | POST /v1/transactions | Returns IngestTransactionResponse. |
GetTransaction(ctx, id) | GET /v1/transactions/{id} | |
ListTransactions(ctx, params) | GET /v1/transactions | Optional SourcePartyID filter. |
IngestTransactionsBatch(ctx, items, opts...) | POST /v1/transactions/batch | NDJSON; 4 MiB guard; returns BatchAccepted. |
IngestPartiesBatch(ctx, items, opts...) | POST /v1/parties/batch | Same NDJSON shape. |
GetBatch(ctx, batchID) | GET /v1/batches/{id} | Returns BatchStatus. |
ListBatchErrors(ctx, batchID, params) | GET /v1/batches/{id}/errors | Returns BatchErrorsPage. |
CancelBatch(ctx, batchID, reason) | POST /v1/batches/{id}/cancel | Pass "" to omit reason. |
Webhook verification
Error handling
Retry policy
Retry is off by default. Enable with WithRetry(aventra.DefaultRetryPolicy) — 3 total attempts, 1 s base delay, 30 s cap, 429 and 5xx only. POST/PATCH/DELETE requests without an Idempotency-Key are never retried (unsafe). The SDK re-signs with a fresh timestamp on each retry, satisfying the server's replay-protection requirement.
TypeScript SDK
Official TypeScript/Node client — stdlib only, zero third-party runtime dependencies, Node 18+.
Install
Requires Node 18+ (uses global fetch and node:crypto).
Quick start
- 1Import
AventraClientfrom"@aventraguard/aml-sdk". - 2Construct:
new AventraClient(baseURL, apiKey, options). The API key is the HMAC signing key — no separate secret. - 3Await any method. All methods are
asyncand return typed results. - 4For writes, pass
{ idempotencyKey: "your-id" }as the last argument to enable safe retries.
All methods
| Method | Endpoint | Notes |
|---|---|---|
createParty(req, opts?) | POST /v1/parties | Returns PartyResponse. |
listParties(params?, opts?) | GET /v1/parties | |
getParty(id, opts?) | GET /v1/parties/{id} | |
getPartyScreeningHits(partyId, params?, opts?) | GET /v1/parties/{id}/screening-hits | |
ingestTransaction(req, opts?) | POST /v1/transactions | |
getTransaction(id, opts?) | GET /v1/transactions/{id} | |
listTransactions(params?, opts?) | GET /v1/transactions | Optional source_party_id filter. |
ingestTransactionsBatch(items, opts?) | POST /v1/transactions/batch | NDJSON; 4 MiB guard; returns BatchAccepted. |
ingestPartiesBatch(items, opts?) | POST /v1/parties/batch | Same shape. |
getBatch(batchId, opts?) | GET /v1/batches/{id} | Returns BatchStatus (camelCase). |
listBatchErrors(batchId, params?, opts?) | GET /v1/batches/{id}/errors | |
cancelBatch(batchId, reason?, opts?) | POST /v1/batches/{id}/cancel | Pass undefined to omit reason. |
Webhook verification
Error handling
Retry policy
Retry is off by default. Enable with retry: DEFAULT_RETRY_POLICY — 3 total attempts, 1 s base delay, 30 s cap, 429 and 5xx only. POST/PATCH/DELETE without idempotencyKey are never retried. The SDK re-signs on each retry. Pass an AbortSignal in CallOptions for per-request cancellation.
Security properties: HTTPS enforced (loopback excepted for local dev); redirects are blocked (redirect: "manual"); all v1= comparisons use timingSafeEqual; success responses bounded to 4 MiB.
Reference
Rate limits
| Operation | Default limit | Window |
|---|---|---|
| Read (GET) | 600 requests / key | Rolling 60 seconds |
| Write (POST / PATCH / DELETE) | 60 requests / key | Rolling 60 seconds |
| Batch body | 4 MiB per request | — |
Response headers: RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset (IETF draft form) plus X-RateLimit-* aliases. Limits are tunable by the operator via environment variables.
Idempotency semantics
- Header:
Idempotency-Key: <up to 128 bytes> - Cache duration: 24 hours from first 2xx response.
- Scope: per API key (not global).
- Replay adds:
Idempotency-Replayed: trueresponse header. - Reusing a key with a different body: 409
idempotency-conflict. - Non-2xx first responses are not cached — the second attempt runs fresh.
Scopes
| Scope | Read/Write | Endpoints |
|---|---|---|
parties:read | Read | GET /v1/parties, GET /v1/parties/{id}, GET /v1/parties/{id}/screening-hits, GET /v1/screening-hits |
parties:write | Write | POST /v1/parties, POST /v1/parties/{id}/recompute, POST /v1/parties/batch |
transactions:read | Read | GET /v1/transactions, GET /v1/transactions/{id}, GET /v1/batches/{id}* |
transactions:write | Write | POST /v1/transactions, POST /v1/transactions/batch, POST /v1/batches/{id}/cancel* |
alerts:read | Read | GET /v1/alerts, GET /v1/alerts/{id} |
alerts:write | Write | PATCH /v1/alerts/{id}/disposition |
cases:read | Read | GET /v1/cases, GET /v1/cases/{id} |
filings:read | Read | GET /v1/filings, GET /v1/filings/{id} |
webhooks:read | Read | GET /v1/webhooks |
webhooks:write | Write | POST /v1/webhooks, DELETE /v1/webhooks/{id} |
audit:read | Read | Audit log (granted on request). |
* Batch lifecycle endpoints require the batch's own resource scope (transactions or parties).
Changelog summary
| Version | Date | Highlights |
|---|---|---|
| Unreleased | — | Bulk batch ingest (NDJSON, ≤4 MiB); batch status/errors/cancel; batch.completed (9th webhook event); party.screening_hit.created (pre-review detection event); GET /v1/screening-hits (bulk reconciliation); GET /v1/transactions[/{id}] read-back; transaction-to-party orphan re-link on party creation; PCMLTFA individual fields (birth_date, government_id_*, occupation, etc.); zero-downtime webhook secret rotation (ADR-0013); list endpoints reject invalid pagination with 400; unsupported methods return 405. |
| v1.2.0 | 2026-06-05 | Critical fix: canonical_direction was hardcoded to credit; fixed direction derivation from action. Critical fix: 503 on all /v1/transactions and /v1/parties calls (schema CHECK constraint). Optional direction field on POST /v1/transactions. 409 on idempotency-key body-hash mismatch. Published HMAC signing test vectors. Rate-limit response headers. Webhook payload schemas (7/7 events). |
| v1.1.0 | 2026-05-30 | Retry curve extended to 7 attempts; permanent 4xx dead-lettering; X-AM-Delivery-Attempt header; per-webhook retry policy override; dead-letter notifications. |
| v1.0.0 | 2026-05-30 | Initial release. 16 endpoints; HMAC-SHA256 signing; idempotency; replay protection; rate limiting; 7 webhook event types; RFC 7807 errors. |
Full changelog: docs/api/changelog.md in the repository. Breaking changes always ship as a new URL prefix (/v2/); the /v1/ contract is never silently broken.