{
  "openapi": "3.0.3",
  "info": {
    "title": "AventraGuard Public API",
    "version": "1.4.1",
    "description": "The AventraGuard Public `/v1` REST API lets a reporting entity (RE) push parties and transactions into the AML platform, read back the screening hits, alerts, cases and STR filings they produce, and manage webhook subscriptions for asynchronous events.\n\n**Authentication & signing (summary).** Every request carries an `X-API-Key` header (`ak_live_*` for production, `ak_test_*` for staging/dev). Writes (`POST`/`PATCH`/`DELETE`) additionally require an HMAC-SHA256 signature: build the string-to-sign as `METHOD\\nPATH_WITH_QUERY\\nTIMESTAMP\\nBODY_SHA256_HEX` (literal `\\n` separators, no trailing newline), compute `hex(HMAC-SHA256(raw_api_key, string_to_sign))`, and send it as `X-Aventra-Signature` together with the matching Unix-epoch `X-Aventra-Timestamp`. The raw API key is the HMAC key; there is no separate signing secret. GET requests need only `X-API-Key` (a signature is optional defence-in-depth). The timestamp must be within 5 minutes of server time and each `(timestamp, signature)` pair is single-use (10-minute replay window). Mutations should carry an `Idempotency-Key` (UUID v4 recommended, max 128 bytes) for safe retry; the 2xx response is cached for 24 hours. Per-key rate limits are 600 reads / 60 writes per minute.\n\n**Scopes.** Each operation lists the scope its API key must hold. The server enforces scopes per endpoint — a key missing the required scope is rejected with `403` `insufficient_scope`. Request only the scopes you need.\n\nThis machine-readable contract complements the narrative documentation. See the prose docs (`docs/api/endpoints.md`, `authentication.md`, `errors.md`, `webhooks.md`) for worked examples, recipes, and the full webhook delivery guide.",
    "contact": {
      "name": "AventraGuard API support",
      "email": "contact@aventraguard.com"
    }
  },
  "servers": [
    {
      "url": "https://api.aventraguard.com",
      "description": "Production. Use live keys (ak_live_). HMAC-signed writes; no docs-origin Try-it by design."
    },
    {
      "url": "https://staging.aventraguard.com",
      "description": "Staging / Sandbox. Use test keys (ak_test_). There is NO separate sandbox host — staging IS the sandbox."
    },
    {
      "url": "https://dev.aventraguard.com",
      "description": "Dev. Use test keys (ak_test_)."
    }
  ],
  "tags": [
    {
      "name": "Status",
      "description": "Unauthenticated health/reachability check."
    },
    {
      "name": "Parties",
      "description": "Create/upsert customer (party) records, read them back, recompute risk tier, and list per-party screening hits."
    },
    {
      "name": "Transactions",
      "description": "Ingest transactions for AML rule evaluation and read them back to confirm/reconcile ingests."
    },
    {
      "name": "Screening Hits",
      "description": "RE-scoped bulk list of sanctions/PEP/adverse-media screening hits for reconciliation."
    },
    {
      "name": "Alerts",
      "description": "Read alerts produced by the rule engine and disposition them as true/false positive."
    },
    {
      "name": "Cases",
      "description": "Read-only access to AML investigation cases."
    },
    {
      "name": "Filings",
      "description": "Read-only access to STR filings. The submission workflow stays in-platform (MLRO four-eyes review)."
    },
    {
      "name": "Batches",
      "description": "Status, per-row errors, and cancellation for asynchronous bulk-ingest batches."
    },
    {
      "name": "Webhooks",
      "description": "Register, list, and delete webhook subscriptions for asynchronous events."
    },
    {
      "name": "Audit Log",
      "description": "Read-only, allow-listed, RE-scoped view of the WORM audit trail. Requires the audit:read scope, which ships dark (granted per credential on request)."
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    },
    {
      "ApiKeyAuth": [],
      "TimestampAuth": [],
      "SignatureAuth": []
    }
  ],
  "paths": {
    "/v1/status": {
      "get": {
        "tags": [
          "Status"
        ],
        "summary": "Service health check",
        "description": "No-auth shallow health endpoint. Confirms AventraGuard is reachable and reports the build the API is running. Cached for 5 seconds. Deep health (DB, downstream) lives at the operator-only `/internal/readyz`. Signing: not required (no auth). Scope: none.",
        "operationId": "getStatus",
        "security": [],
        "responses": {
          "200": {
            "description": "Service is reachable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl -sS \"https://api.aventraguard.com/v1/status\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.get(\"https://api.aventraguard.com/v1/status\")\nresp.raise_for_status()\nprint(resp.json())"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"io\"\n    \"net/http\"\n)\n\nresp, _ := http.Get(\"https://api.aventraguard.com/v1/status\")\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "const resp = await fetch(\"https://api.aventraguard.com/v1/status\");\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$ch = curl_init(\"https://api.aventraguard.com/v1/status\");\ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import java.net.URI;\nimport java.net.http.*;\n\nvar client = HttpClient.newHttpClient();\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/status\"))\n    .GET()\n    .build();\nHttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/parties": {
      "post": {
        "tags": [
          "Parties"
        ],
        "summary": "Create or upsert a party",
        "description": "Insert a customer (party) record, or update if the `(re_id, source_system='public_api', source_party_id)` triple already exists. Creating/updating a party back-links any of its previously-orphaned transactions. Signing: required (POST). Idempotency-Key: supported. Scope: `parties:write`.",
        "operationId": "createParty",
        "security": [
          {
            "ApiKeyAuth": [],
            "TimestampAuth": [],
            "SignatureAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePartyRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Party created or upserted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Party"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "API_KEY=\"ak_live_...\"\nBODY='{\"source_party_id\":\"acme-cust-00001\",\"party_type\":\"individual\",\"given_name\":\"Alex\",\"family_name\":\"Morgan\",\"address_country\":\"CA\"}'\nTS=$(date +%s)\nBODY_HASH=$(printf \"%s\" \"$BODY\" | openssl dgst -sha256 -hex | awk '{print $2}')\nSIG=$(printf \"%b\" \"POST\\n/v1/parties\\n${TS}\\n${BODY_HASH}\" | openssl dgst -sha256 -hmac \"$API_KEY\" -hex | awk '{print $2}')\n\ncurl -sS \"https://api.aventraguard.com/v1/parties\" \\\n  -H \"X-API-Key: $API_KEY\" \\\n  -H \"X-Aventra-Timestamp: $TS\" \\\n  -H \"X-Aventra-Signature: $SIG\" \\\n  -H \"Idempotency-Key: idem-acme-cust-00001\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"$BODY\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import hashlib, hmac, time, requests\n\nbody    = '{\"source_party_id\":\"acme-cust-00001\",\"party_type\":\"individual\",\"given_name\":\"Alex\",\"family_name\":\"Morgan\",\"address_country\":\"CA\"}'\nts      = str(int(time.time()))\nbh      = hashlib.sha256(body.encode()).hexdigest()\nto_sign = f\"POST\\n/v1/parties\\n{ts}\\n{bh}\"\nsig     = hmac.new(\"ak_live_...\".encode(), to_sign.encode(), hashlib.sha256).hexdigest()\nresp    = requests.post(\n    \"https://api.aventraguard.com/v1/parties\",\n    headers={\"X-API-Key\": \"ak_live_...\", \"X-Aventra-Timestamp\": ts,\n             \"X-Aventra-Signature\": sig, \"Idempotency-Key\": \"idem-acme-cust-00001\",\n             \"Content-Type\": \"application/json\"},\n    data=body,\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"bytes\"\n    \"crypto/hmac\"\n    \"crypto/sha256\"\n    \"encoding/hex\"\n    \"fmt\"\n    \"io\"\n    \"net/http\"\n    \"strconv\"\n    \"time\"\n)\n\nconst apiKey = \"ak_live_...\"\n\nbody := `{\"source_party_id\":\"acme-cust-00001\",\"party_type\":\"individual\",\"given_name\":\"Alex\",\"family_name\":\"Morgan\",\"address_country\":\"CA\"}`\nts := strconv.FormatInt(time.Now().Unix(), 10)\nh := sha256.Sum256([]byte(body))\ntoSign := fmt.Sprintf(\"POST\\n/v1/parties\\n%s\\n%s\", ts, hex.EncodeToString(h[:]))\nmac := hmac.New(sha256.New, []byte(apiKey))\nmac.Write([]byte(toSign))\nsig := hex.EncodeToString(mac.Sum(nil))\n\nreq, _ := http.NewRequest(\"POST\", \"https://api.aventraguard.com/v1/parties\", bytes.NewBufferString(body))\nreq.Header.Set(\"X-API-Key\", apiKey)\nreq.Header.Set(\"X-Aventra-Timestamp\", ts)\nreq.Header.Set(\"X-Aventra-Signature\", sig)\nreq.Header.Set(\"Idempotency-Key\", \"idem-acme-cust-00001\")\nreq.Header.Set(\"Content-Type\", \"application/json\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "import { createHash, createHmac } from \"node:crypto\";\n\nconst apiKey = \"ak_live_...\";\nconst body = '{\"source_party_id\":\"acme-cust-00001\",\"party_type\":\"individual\",\"given_name\":\"Alex\",\"family_name\":\"Morgan\",\"address_country\":\"CA\"}';\nconst ts = String(Math.floor(Date.now() / 1000));\nconst bodyHash = createHash(\"sha256\").update(body).digest(\"hex\");\nconst toSign = `POST\\n/v1/parties\\n${ts}\\n${bodyHash}`;\nconst sig = createHmac(\"sha256\", apiKey).update(toSign).digest(\"hex\");\n\nconst resp = await fetch(\"https://api.aventraguard.com/v1/parties\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": apiKey,\n    \"X-Aventra-Timestamp\": ts,\n    \"X-Aventra-Signature\": sig,\n    \"Idempotency-Key\": \"idem-acme-cust-00001\",\n    \"Content-Type\": \"application/json\",\n  },\n  body,\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$key    = \"ak_live_...\";\n$body   = '{\"source_party_id\":\"acme-cust-00001\",\"party_type\":\"individual\",\"given_name\":\"Alex\",\"family_name\":\"Morgan\",\"address_country\":\"CA\"}';\n$ts     = (string) time();\n$bh     = hash('sha256', $body);\n$sig    = hash_hmac('sha256', \"POST\\n/v1/parties\\n$ts\\n$bh\", $key);\n$ch     = curl_init(\"https://api.aventraguard.com/v1/parties\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POST           => true,\n    CURLOPT_POSTFIELDS     => $body,\n    CURLOPT_HTTPHEADER     => [\n        \"X-API-Key: $key\",\n        \"X-Aventra-Timestamp: $ts\",\n        \"X-Aventra-Signature: $sig\",\n        \"Idempotency-Key: idem-acme-cust-00001\",\n        \"Content-Type: application/json\",\n    ],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.net.URI; import java.net.http.*;\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\n// Java 17+ (java.util.HexFormat)\nimport java.time.Instant; import java.util.HexFormat;\n\nString apiKey = \"ak_live_...\";\nString body   = \"{\\\"source_party_id\\\":\\\"acme-cust-00001\\\",\\\"party_type\\\":\\\"individual\\\",\\\"given_name\\\":\\\"Alex\\\",\\\"family_name\\\":\\\"Morgan\\\",\\\"address_country\\\":\\\"CA\\\"}\";\nString ts     = String.valueOf(Instant.now().getEpochSecond());\nString bHash  = HexFormat.of().formatHex(\n    MessageDigest.getInstance(\"SHA-256\").digest(body.getBytes(StandardCharsets.UTF_8)));\nMac mac = Mac.getInstance(\"HmacSHA256\");\nmac.init(new SecretKeySpec(apiKey.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"));\nString sig = HexFormat.of().formatHex(mac.doFinal(\n    (\"POST\\n/v1/parties\\n\" + ts + \"\\n\" + bHash).getBytes(StandardCharsets.UTF_8)));\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/parties\"))\n    .header(\"X-API-Key\", apiKey).header(\"X-Aventra-Timestamp\", ts)\n    .header(\"X-Aventra-Signature\", sig)\n    .header(\"Idempotency-Key\", \"idem-acme-cust-00001\")\n    .header(\"Content-Type\", \"application/json\")\n    .method(\"POST\", HttpRequest.BodyPublishers.ofString(body))\n    .build();\nHttpResponse<String> resp =\n    HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      },
      "get": {
        "tags": [
          "Parties"
        ],
        "summary": "List parties",
        "description": "Newest-first by default, cursor-paginated list of parties. Signing: optional (GET). Scope: `parties:read`.",
        "operationId": "listParties",
        "parameters": [
          {
            "$ref": "#/components/parameters/LimitParam"
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          },
          {
            "$ref": "#/components/parameters/OrderParam"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of parties.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PartyListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl -sS \"https://api.aventraguard.com/v1/parties\" \\\n  -H \"X-API-Key: ak_live_...\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.aventraguard.com/v1/parties\",\n    headers={\"X-API-Key\": \"ak_live_...\"},\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"io\"\n    \"net/http\"\n)\n\nreq, _ := http.NewRequest(\"GET\", \"https://api.aventraguard.com/v1/parties\", nil)\nreq.Header.Set(\"X-API-Key\", \"ak_live_...\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "const resp = await fetch(\"https://api.aventraguard.com/v1/parties\", {\n  headers: { \"X-API-Key\": \"ak_live_...\" },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$ch = curl_init(\"https://api.aventraguard.com/v1/parties\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\"X-API-Key: ak_live_...\"],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import java.net.URI;\nimport java.net.http.*;\n\nvar client = HttpClient.newHttpClient();\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/parties\"))\n    .header(\"X-API-Key\", \"ak_live_...\")\n    .GET()\n    .build();\nHttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/parties/batch": {
      "post": {
        "tags": [
          "Parties"
        ],
        "summary": "Bulk-ingest parties (NDJSON)",
        "description": "Back-fill many parties in one request as NDJSON (one `POST /v1/parties` body per line, max 4 MiB). Each line is validated; valid rows are enqueued for asynchronous processing, invalid rows recorded as `failed` with a reason. Returns `202` with a `batch_id` + `status_url` — poll `GET /v1/batches/{id}` (resource `parties`) or await the `batch.completed` webhook. The worker upserts each party (PII encrypted at rest) and re-links orphan transactions. A per-batch `Idempotency-Key` dedups whole chunks (replay returns the same `batch_id`). Blank lines are ignored. Signing: required (POST). Scope: `parties:write`.",
        "operationId": "createPartiesBatch",
        "security": [
          {
            "ApiKeyAuth": [],
            "TimestampAuth": [],
            "SignatureAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-ndjson": {
              "schema": {
                "type": "string",
                "description": "NDJSON: one CreatePartyRequest JSON object per line. Max 4 MiB.",
                "example": "{\"source_party_id\":\"p-1\",\"party_type\":\"individual\",\"given_name\":\"A\",\"family_name\":\"B\"}\n{\"source_party_id\":\"p-2\",\"party_type\":\"business\",\"business_name\":\"Acme\"}\n"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Batch accepted (valid + invalid rows recorded). A 202 is NOT confirmation that every row landed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchAccepted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "API_KEY=\"ak_live_...\"\nTS=$(date +%s)\nBODY_HASH=$(openssl dgst -sha256 -hex < parties.ndjson | awk '{print $2}')\nSIG=$(printf \"%b\" \"POST\\n/v1/parties/batch\\n${TS}\\n${BODY_HASH}\" | openssl dgst -sha256 -hmac \"$API_KEY\" -hex | awk '{print $2}')\n\ncurl -sS \"https://api.aventraguard.com/v1/parties/batch\" \\\n  -H \"X-API-Key: $API_KEY\" \\\n  -H \"X-Aventra-Timestamp: $TS\" \\\n  -H \"X-Aventra-Signature: $SIG\" \\\n  -H \"Idempotency-Key: parties-batch-2026-06-01\" \\\n  -H \"Content-Type: application/x-ndjson\" \\\n  --data-binary @parties.ndjson"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import hashlib, hmac, time, requests\n\nwith open('parties.ndjson', 'rb') as f:\n    body = f.read()\nts      = str(int(time.time()))\nbh      = hashlib.sha256(body).hexdigest()\nto_sign = f\"POST\\n/v1/parties/batch\\n{ts}\\n{bh}\"\nsig     = hmac.new(\"ak_live_...\".encode(), to_sign.encode(), hashlib.sha256).hexdigest()\nresp    = requests.post(\n    \"https://api.aventraguard.com/v1/parties/batch\",\n    headers={\"X-API-Key\": \"ak_live_...\", \"X-Aventra-Timestamp\": ts,\n             \"X-Aventra-Signature\": sig, \"Idempotency-Key\": \"parties-batch-2026-06-01\",\n             \"Content-Type\": \"application/x-ndjson\"},\n    data=body,\n)\nresp.raise_for_status()\nbatch = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"bytes\"\n    \"crypto/hmac\"\n    \"crypto/sha256\"\n    \"encoding/hex\"\n    \"fmt\"\n    \"io\"\n    \"net/http\"\n    \"os\"\n    \"strconv\"\n    \"time\"\n)\n\nconst apiKey = \"ak_live_...\"\n\nbody, _ := os.ReadFile(\"parties.ndjson\")\nts := strconv.FormatInt(time.Now().Unix(), 10)\nh := sha256.Sum256(body)\ntoSign := fmt.Sprintf(\"POST\\n/v1/parties/batch\\n%s\\n%s\", ts, hex.EncodeToString(h[:]))\nmac := hmac.New(sha256.New, []byte(apiKey))\nmac.Write([]byte(toSign))\nsig := hex.EncodeToString(mac.Sum(nil))\n\nreq, _ := http.NewRequest(\"POST\", \"https://api.aventraguard.com/v1/parties/batch\", bytes.NewReader(body))\nreq.Header.Set(\"X-API-Key\", apiKey)\nreq.Header.Set(\"X-Aventra-Timestamp\", ts)\nreq.Header.Set(\"X-Aventra-Signature\", sig)\nreq.Header.Set(\"Idempotency-Key\", \"parties-batch-2026-06-01\")\nreq.Header.Set(\"Content-Type\", \"application/x-ndjson\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "import { createHash, createHmac } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\n\nconst apiKey = \"ak_live_...\";\nconst body = readFileSync(\"parties.ndjson\");\nconst ts = String(Math.floor(Date.now() / 1000));\nconst bodyHash = createHash(\"sha256\").update(body).digest(\"hex\");\nconst toSign = `POST\\n/v1/parties/batch\\n${ts}\\n${bodyHash}`;\nconst sig = createHmac(\"sha256\", apiKey).update(toSign).digest(\"hex\");\n\nconst resp = await fetch(\"https://api.aventraguard.com/v1/parties/batch\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": apiKey,\n    \"X-Aventra-Timestamp\": ts,\n    \"X-Aventra-Signature\": sig,\n    \"Idempotency-Key\": \"parties-batch-2026-06-01\",\n    \"Content-Type\": \"application/x-ndjson\",\n  },\n  body,\n});\nconst batch = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$key    = \"ak_live_...\";\n$body   = file_get_contents('parties.ndjson');\n$ts     = (string) time();\n$bh     = hash('sha256', $body);\n$sig    = hash_hmac('sha256', \"POST\\n/v1/parties/batch\\n$ts\\n$bh\", $key);\n$ch     = curl_init(\"https://api.aventraguard.com/v1/parties/batch\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POST           => true,\n    CURLOPT_POSTFIELDS     => $body,\n    CURLOPT_HTTPHEADER     => [\n        \"X-API-Key: $key\",\n        \"X-Aventra-Timestamp: $ts\",\n        \"X-Aventra-Signature: $sig\",\n        \"Idempotency-Key: parties-batch-2026-06-01\",\n        \"Content-Type: application/x-ndjson\",\n    ],\n]);\n$batch = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.net.URI; import java.net.http.*;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.*;\nimport java.security.MessageDigest;\n// Java 17+ (java.util.HexFormat)\nimport java.time.Instant; import java.util.HexFormat;\n\nString apiKey = \"ak_live_...\";\nbyte[] body   = Files.readAllBytes(Path.of(\"parties.ndjson\"));\nString ts     = String.valueOf(Instant.now().getEpochSecond());\nString bHash  = HexFormat.of().formatHex(MessageDigest.getInstance(\"SHA-256\").digest(body));\nMac mac = Mac.getInstance(\"HmacSHA256\");\nmac.init(new SecretKeySpec(apiKey.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"));\nString sig = HexFormat.of().formatHex(mac.doFinal(\n    (\"POST\\n/v1/parties/batch\\n\" + ts + \"\\n\" + bHash).getBytes(StandardCharsets.UTF_8)));\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/parties/batch\"))\n    .header(\"X-API-Key\", apiKey).header(\"X-Aventra-Timestamp\", ts)\n    .header(\"X-Aventra-Signature\", sig)\n    .header(\"Idempotency-Key\", \"parties-batch-2026-06-01\")\n    .header(\"Content-Type\", \"application/x-ndjson\")\n    .POST(HttpRequest.BodyPublishers.ofByteArray(body))\n    .build();\nHttpResponse<String> resp =\n    HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/parties/{id}": {
      "get": {
        "tags": [
          "Parties"
        ],
        "summary": "Get a party",
        "description": "Fetch a single party by its numeric AventraGuard party ID (not your `source_party_id`). RE-scoped: an unknown or other-RE id returns 404. Signing: optional (GET). Scope: `parties:read`.",
        "operationId": "getParty",
        "parameters": [
          {
            "$ref": "#/components/parameters/PartyIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "The party.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Party"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl -sS \"https://api.aventraguard.com/v1/parties/10421\" \\\n  -H \"X-API-Key: ak_live_...\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.aventraguard.com/v1/parties/10421\",\n    headers={\"X-API-Key\": \"ak_live_...\"},\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"io\"\n    \"net/http\"\n)\n\nreq, _ := http.NewRequest(\"GET\", \"https://api.aventraguard.com/v1/parties/10421\", nil)\nreq.Header.Set(\"X-API-Key\", \"ak_live_...\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "const resp = await fetch(\"https://api.aventraguard.com/v1/parties/10421\", {\n  headers: { \"X-API-Key\": \"ak_live_...\" },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$ch = curl_init(\"https://api.aventraguard.com/v1/parties/10421\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\"X-API-Key: ak_live_...\"],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import java.net.URI;\nimport java.net.http.*;\n\nvar client = HttpClient.newHttpClient();\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/parties/10421\"))\n    .header(\"X-API-Key\", \"ak_live_...\")\n    .GET()\n    .build();\nHttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/parties/{id}/recompute": {
      "post": {
        "tags": [
          "Parties"
        ],
        "summary": "Recompute a party's risk tier",
        "description": "Trigger an immediate re-evaluation of the party's risk tier using the current configuration (jurisdiction risk, PEP screening, transaction velocity, etc.). The request body is ignored (send `{}` or none). Downgrades require MLRO approval, so they are queued (`downgrade_pending`). Signing: required (POST). Idempotency-Key: supported. Scope: `parties:write`.",
        "operationId": "recomputePartyRiskTier",
        "security": [
          {
            "ApiKeyAuth": [],
            "TimestampAuth": [],
            "SignatureAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PartyIdPath"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKeyHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "Recompute ran.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecomputeResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "API_KEY=\"ak_live_...\"\nBODY='{}'\nTS=$(date +%s)\nBODY_HASH=$(printf \"%s\" \"$BODY\" | openssl dgst -sha256 -hex | awk '{print $2}')\nSIG=$(printf \"%b\" \"POST\\n/v1/parties/10421/recompute\\n${TS}\\n${BODY_HASH}\" | openssl dgst -sha256 -hmac \"$API_KEY\" -hex | awk '{print $2}')\n\ncurl -sS \"https://api.aventraguard.com/v1/parties/10421/recompute\" \\\n  -H \"X-API-Key: $API_KEY\" \\\n  -H \"X-Aventra-Timestamp: $TS\" \\\n  -H \"X-Aventra-Signature: $SIG\" \\\n  -H \"Idempotency-Key: recompute-10421\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"$BODY\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import hashlib, hmac, time, requests\n\nbody    = '{}'\nts      = str(int(time.time()))\nbh      = hashlib.sha256(body.encode()).hexdigest()\nto_sign = f\"POST\\n/v1/parties/10421/recompute\\n{ts}\\n{bh}\"\nsig     = hmac.new(\"ak_live_...\".encode(), to_sign.encode(), hashlib.sha256).hexdigest()\nresp    = requests.post(\n    \"https://api.aventraguard.com/v1/parties/10421/recompute\",\n    headers={\"X-API-Key\": \"ak_live_...\", \"X-Aventra-Timestamp\": ts,\n             \"X-Aventra-Signature\": sig, \"Idempotency-Key\": \"recompute-10421\",\n             \"Content-Type\": \"application/json\"},\n    data=body,\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"bytes\"\n    \"crypto/hmac\"\n    \"crypto/sha256\"\n    \"encoding/hex\"\n    \"fmt\"\n    \"io\"\n    \"net/http\"\n    \"strconv\"\n    \"time\"\n)\n\nconst apiKey = \"ak_live_...\"\n\nbody := `{}`\nts := strconv.FormatInt(time.Now().Unix(), 10)\nh := sha256.Sum256([]byte(body))\ntoSign := fmt.Sprintf(\"POST\\n/v1/parties/10421/recompute\\n%s\\n%s\", ts, hex.EncodeToString(h[:]))\nmac := hmac.New(sha256.New, []byte(apiKey))\nmac.Write([]byte(toSign))\nsig := hex.EncodeToString(mac.Sum(nil))\n\nreq, _ := http.NewRequest(\"POST\", \"https://api.aventraguard.com/v1/parties/10421/recompute\", bytes.NewBufferString(body))\nreq.Header.Set(\"X-API-Key\", apiKey)\nreq.Header.Set(\"X-Aventra-Timestamp\", ts)\nreq.Header.Set(\"X-Aventra-Signature\", sig)\nreq.Header.Set(\"Idempotency-Key\", \"recompute-10421\")\nreq.Header.Set(\"Content-Type\", \"application/json\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "import { createHash, createHmac } from \"node:crypto\";\n\nconst apiKey = \"ak_live_...\";\nconst body = '{}';\nconst ts = String(Math.floor(Date.now() / 1000));\nconst bodyHash = createHash(\"sha256\").update(body).digest(\"hex\");\nconst toSign = `POST\\n/v1/parties/10421/recompute\\n${ts}\\n${bodyHash}`;\nconst sig = createHmac(\"sha256\", apiKey).update(toSign).digest(\"hex\");\n\nconst resp = await fetch(\"https://api.aventraguard.com/v1/parties/10421/recompute\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": apiKey,\n    \"X-Aventra-Timestamp\": ts,\n    \"X-Aventra-Signature\": sig,\n    \"Idempotency-Key\": \"recompute-10421\",\n    \"Content-Type\": \"application/json\",\n  },\n  body,\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$key    = \"ak_live_...\";\n$body   = '{}';\n$ts     = (string) time();\n$bh     = hash('sha256', $body);\n$sig    = hash_hmac('sha256', \"POST\\n/v1/parties/10421/recompute\\n$ts\\n$bh\", $key);\n$ch     = curl_init(\"https://api.aventraguard.com/v1/parties/10421/recompute\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POST           => true,\n    CURLOPT_POSTFIELDS     => $body,\n    CURLOPT_HTTPHEADER     => [\n        \"X-API-Key: $key\",\n        \"X-Aventra-Timestamp: $ts\",\n        \"X-Aventra-Signature: $sig\",\n        \"Idempotency-Key: recompute-10421\",\n        \"Content-Type: application/json\",\n    ],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.net.URI; import java.net.http.*;\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\n// Java 17+ (java.util.HexFormat)\nimport java.time.Instant; import java.util.HexFormat;\n\nString apiKey = \"ak_live_...\";\nString body   = \"{}\";\nString ts     = String.valueOf(Instant.now().getEpochSecond());\nString bHash  = HexFormat.of().formatHex(\n    MessageDigest.getInstance(\"SHA-256\").digest(body.getBytes(StandardCharsets.UTF_8)));\nMac mac = Mac.getInstance(\"HmacSHA256\");\nmac.init(new SecretKeySpec(apiKey.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"));\nString sig = HexFormat.of().formatHex(mac.doFinal(\n    (\"POST\\n/v1/parties/10421/recompute\\n\" + ts + \"\\n\" + bHash).getBytes(StandardCharsets.UTF_8)));\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/parties/10421/recompute\"))\n    .header(\"X-API-Key\", apiKey).header(\"X-Aventra-Timestamp\", ts)\n    .header(\"X-Aventra-Signature\", sig)\n    .header(\"Idempotency-Key\", \"recompute-10421\")\n    .header(\"Content-Type\", \"application/json\")\n    .method(\"POST\", HttpRequest.BodyPublishers.ofString(body))\n    .build();\nHttpResponse<String> resp =\n    HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/parties/{id}/screening-hits": {
      "get": {
        "tags": [
          "Parties"
        ],
        "summary": "List a party's screening hits",
        "description": "Returns the active sanctions / PEP / adverse-media hits attached to the party, including full match detail (matched/screened names, scores). These name fields are PCMLTFA s.66 tipping-off sensitive — strip them at your transform layer if you persist them. For a non-tipping-off bulk list across all parties, use `GET /v1/screening-hits`. Signing: optional (GET). Scope: `parties:read`.",
        "operationId": "listPartyScreeningHits",
        "parameters": [
          {
            "$ref": "#/components/parameters/PartyIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "The party's screening hits (full detail).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScreeningHitListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl -sS \"https://api.aventraguard.com/v1/parties/10421/screening-hits\" \\\n  -H \"X-API-Key: ak_live_...\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.aventraguard.com/v1/parties/10421/screening-hits\",\n    headers={\"X-API-Key\": \"ak_live_...\"},\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"io\"\n    \"net/http\"\n)\n\nreq, _ := http.NewRequest(\"GET\", \"https://api.aventraguard.com/v1/parties/10421/screening-hits\", nil)\nreq.Header.Set(\"X-API-Key\", \"ak_live_...\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "const resp = await fetch(\"https://api.aventraguard.com/v1/parties/10421/screening-hits\", {\n  headers: { \"X-API-Key\": \"ak_live_...\" },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$ch = curl_init(\"https://api.aventraguard.com/v1/parties/10421/screening-hits\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\"X-API-Key: ak_live_...\"],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import java.net.URI;\nimport java.net.http.*;\n\nvar client = HttpClient.newHttpClient();\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/parties/10421/screening-hits\"))\n    .header(\"X-API-Key\", \"ak_live_...\")\n    .GET()\n    .build();\nHttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/screening-hits": {
      "get": {
        "tags": [
          "Screening Hits"
        ],
        "summary": "List all screening hits for your RE",
        "description": "RE-scoped, newest-first, cursor-paginated bulk list for reconciliation / catch-up after downtime, with an optional `since` (RFC 3339) incremental filter. Deliberately a non-tipping-off subset: it omits the matched name, list-entry name, and screened name. For full match detail on one party use `GET /v1/parties/{id}/screening-hits`. `hit_id` correlates with the `party.screening_hit` webhook. Signing: optional (GET). Scope: `parties:read`.",
        "operationId": "listScreeningHits",
        "parameters": [
          {
            "$ref": "#/components/parameters/LimitParam"
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          },
          {
            "$ref": "#/components/parameters/SinceParam"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of screening hits (non-tipping-off subset).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkScreeningHitListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl -sS \"https://api.aventraguard.com/v1/screening-hits?since=2026-06-01T00%3A00%3A00Z\" \\\n  -H \"X-API-Key: ak_live_...\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.aventraguard.com/v1/screening-hits?since=2026-06-01T00%3A00%3A00Z\",\n    headers={\"X-API-Key\": \"ak_live_...\"},\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"io\"\n    \"net/http\"\n)\n\nreq, _ := http.NewRequest(\"GET\", \"https://api.aventraguard.com/v1/screening-hits?since=2026-06-01T00%3A00%3A00Z\", nil)\nreq.Header.Set(\"X-API-Key\", \"ak_live_...\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "const resp = await fetch(\"https://api.aventraguard.com/v1/screening-hits?since=2026-06-01T00%3A00%3A00Z\", {\n  headers: { \"X-API-Key\": \"ak_live_...\" },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$ch = curl_init(\"https://api.aventraguard.com/v1/screening-hits?since=2026-06-01T00%3A00%3A00Z\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\"X-API-Key: ak_live_...\"],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import java.net.URI;\nimport java.net.http.*;\n\nvar client = HttpClient.newHttpClient();\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/screening-hits?since=2026-06-01T00%3A00%3A00Z\"))\n    .header(\"X-API-Key\", \"ak_live_...\")\n    .GET()\n    .build();\nHttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/transactions": {
      "post": {
        "tags": [
          "Transactions"
        ],
        "summary": "Ingest a transaction",
        "description": "Push a single transaction for AML rule evaluation. Persisted then queued for asynchronous screening; responds `202 Accepted` once persisted. Listen on the `alert.created` webhook (or poll `/v1/alerts`) for any alerts produced. A transaction may reference a `source_party_id` whose party doesn't exist yet — it links once the party is pushed. Signing: required (POST). Idempotency-Key: supported. Scope: `transactions:write`.",
        "operationId": "ingestTransaction",
        "security": [
          {
            "ApiKeyAuth": [],
            "TimestampAuth": [],
            "SignatureAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IngestTransactionRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted; screening will run asynchronously.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IngestTransactionResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "API_KEY=\"ak_live_...\"\nBODY='{\"source_transaction_id\":\"acme-tx-2026-05-30-0001\",\"source_party_id\":\"acme-cust-00001\",\"amount\":\"9850.00\",\"currency\":\"CAD\",\"status\":\"completed\",\"method\":\"e_transfer\",\"action\":\"transfer\",\"occurred_at\":\"2026-06-01T09:00:00Z\",\"source_created_at\":\"2026-06-01T09:00:01Z\"}'\nTS=$(date +%s)\nBODY_HASH=$(printf \"%s\" \"$BODY\" | openssl dgst -sha256 -hex | awk '{print $2}')\nSIG=$(printf \"%b\" \"POST\\n/v1/transactions\\n${TS}\\n${BODY_HASH}\" | openssl dgst -sha256 -hmac \"$API_KEY\" -hex | awk '{print $2}')\n\ncurl -sS \"https://api.aventraguard.com/v1/transactions\" \\\n  -H \"X-API-Key: $API_KEY\" \\\n  -H \"X-Aventra-Timestamp: $TS\" \\\n  -H \"X-Aventra-Signature: $SIG\" \\\n  -H \"Idempotency-Key: idem-acme-tx-0001\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"$BODY\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import hashlib, hmac, time, requests\n\nbody    = '{\"source_transaction_id\":\"acme-tx-2026-05-30-0001\",\"source_party_id\":\"acme-cust-00001\",\"amount\":\"9850.00\",\"currency\":\"CAD\",\"status\":\"completed\",\"method\":\"e_transfer\",\"action\":\"transfer\",\"occurred_at\":\"2026-06-01T09:00:00Z\",\"source_created_at\":\"2026-06-01T09:00:01Z\"}'\nts      = str(int(time.time()))\nbh      = hashlib.sha256(body.encode()).hexdigest()\nto_sign = f\"POST\\n/v1/transactions\\n{ts}\\n{bh}\"\nsig     = hmac.new(\"ak_live_...\".encode(), to_sign.encode(), hashlib.sha256).hexdigest()\nresp    = requests.post(\n    \"https://api.aventraguard.com/v1/transactions\",\n    headers={\"X-API-Key\": \"ak_live_...\", \"X-Aventra-Timestamp\": ts,\n             \"X-Aventra-Signature\": sig, \"Idempotency-Key\": \"idem-acme-tx-0001\",\n             \"Content-Type\": \"application/json\"},\n    data=body,\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"bytes\"\n    \"crypto/hmac\"\n    \"crypto/sha256\"\n    \"encoding/hex\"\n    \"fmt\"\n    \"io\"\n    \"net/http\"\n    \"strconv\"\n    \"time\"\n)\n\nconst apiKey = \"ak_live_...\"\n\nbody := `{\"source_transaction_id\":\"acme-tx-2026-05-30-0001\",\"source_party_id\":\"acme-cust-00001\",\"amount\":\"9850.00\",\"currency\":\"CAD\",\"status\":\"completed\",\"method\":\"e_transfer\",\"action\":\"transfer\",\"occurred_at\":\"2026-06-01T09:00:00Z\",\"source_created_at\":\"2026-06-01T09:00:01Z\"}`\nts := strconv.FormatInt(time.Now().Unix(), 10)\nh := sha256.Sum256([]byte(body))\ntoSign := fmt.Sprintf(\"POST\\n/v1/transactions\\n%s\\n%s\", ts, hex.EncodeToString(h[:]))\nmac := hmac.New(sha256.New, []byte(apiKey))\nmac.Write([]byte(toSign))\nsig := hex.EncodeToString(mac.Sum(nil))\n\nreq, _ := http.NewRequest(\"POST\", \"https://api.aventraguard.com/v1/transactions\", bytes.NewBufferString(body))\nreq.Header.Set(\"X-API-Key\", apiKey)\nreq.Header.Set(\"X-Aventra-Timestamp\", ts)\nreq.Header.Set(\"X-Aventra-Signature\", sig)\nreq.Header.Set(\"Idempotency-Key\", \"idem-acme-tx-0001\")\nreq.Header.Set(\"Content-Type\", \"application/json\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "import { createHash, createHmac } from \"node:crypto\";\n\nconst apiKey = \"ak_live_...\";\nconst body = '{\"source_transaction_id\":\"acme-tx-2026-05-30-0001\",\"source_party_id\":\"acme-cust-00001\",\"amount\":\"9850.00\",\"currency\":\"CAD\",\"status\":\"completed\",\"method\":\"e_transfer\",\"action\":\"transfer\",\"occurred_at\":\"2026-06-01T09:00:00Z\",\"source_created_at\":\"2026-06-01T09:00:01Z\"}';\nconst ts = String(Math.floor(Date.now() / 1000));\nconst bodyHash = createHash(\"sha256\").update(body).digest(\"hex\");\nconst toSign = `POST\\n/v1/transactions\\n${ts}\\n${bodyHash}`;\nconst sig = createHmac(\"sha256\", apiKey).update(toSign).digest(\"hex\");\n\nconst resp = await fetch(\"https://api.aventraguard.com/v1/transactions\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": apiKey,\n    \"X-Aventra-Timestamp\": ts,\n    \"X-Aventra-Signature\": sig,\n    \"Idempotency-Key\": \"idem-acme-tx-0001\",\n    \"Content-Type\": \"application/json\",\n  },\n  body,\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$key    = \"ak_live_...\";\n$body   = '{\"source_transaction_id\":\"acme-tx-2026-05-30-0001\",\"source_party_id\":\"acme-cust-00001\",\"amount\":\"9850.00\",\"currency\":\"CAD\",\"status\":\"completed\",\"method\":\"e_transfer\",\"action\":\"transfer\",\"occurred_at\":\"2026-06-01T09:00:00Z\",\"source_created_at\":\"2026-06-01T09:00:01Z\"}';\n$ts     = (string) time();\n$bh     = hash('sha256', $body);\n$sig    = hash_hmac('sha256', \"POST\\n/v1/transactions\\n$ts\\n$bh\", $key);\n$ch     = curl_init(\"https://api.aventraguard.com/v1/transactions\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POST           => true,\n    CURLOPT_POSTFIELDS     => $body,\n    CURLOPT_HTTPHEADER     => [\n        \"X-API-Key: $key\",\n        \"X-Aventra-Timestamp: $ts\",\n        \"X-Aventra-Signature: $sig\",\n        \"Idempotency-Key: idem-acme-tx-0001\",\n        \"Content-Type: application/json\",\n    ],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.net.URI; import java.net.http.*;\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\n// Java 17+ (java.util.HexFormat)\nimport java.time.Instant; import java.util.HexFormat;\n\nString apiKey = \"ak_live_...\";\nString body   = \"{\\\"source_transaction_id\\\":\\\"acme-tx-2026-05-30-0001\\\",\\\"source_party_id\\\":\\\"acme-cust-00001\\\",\\\"amount\\\":\\\"9850.00\\\",\\\"currency\\\":\\\"CAD\\\",\\\"status\\\":\\\"completed\\\",\\\"method\\\":\\\"e_transfer\\\",\\\"action\\\":\\\"transfer\\\",\\\"occurred_at\\\":\\\"2026-06-01T09:00:00Z\\\",\\\"source_created_at\\\":\\\"2026-06-01T09:00:01Z\\\"}\";\nString ts     = String.valueOf(Instant.now().getEpochSecond());\nString bHash  = HexFormat.of().formatHex(\n    MessageDigest.getInstance(\"SHA-256\").digest(body.getBytes(StandardCharsets.UTF_8)));\nMac mac = Mac.getInstance(\"HmacSHA256\");\nmac.init(new SecretKeySpec(apiKey.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"));\nString sig = HexFormat.of().formatHex(mac.doFinal(\n    (\"POST\\n/v1/transactions\\n\" + ts + \"\\n\" + bHash).getBytes(StandardCharsets.UTF_8)));\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/transactions\"))\n    .header(\"X-API-Key\", apiKey).header(\"X-Aventra-Timestamp\", ts)\n    .header(\"X-Aventra-Signature\", sig)\n    .header(\"Idempotency-Key\", \"idem-acme-tx-0001\")\n    .header(\"Content-Type\", \"application/json\")\n    .method(\"POST\", HttpRequest.BodyPublishers.ofString(body))\n    .build();\nHttpResponse<String> resp =\n    HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      },
      "get": {
        "tags": [
          "Transactions"
        ],
        "summary": "List transactions",
        "description": "Newest-first, cursor-paginated read-back so you can confirm ingests landed and reconcile. Optional `source_party_id` filter lists one party's transactions. RE-scoped, public_api-only, excludes soft-deleted rows; `raw_payload` and card/bank details are not echoed. Signing: optional (GET). Scope: `transactions:read`.",
        "operationId": "listTransactions",
        "parameters": [
          {
            "$ref": "#/components/parameters/LimitParam"
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          },
          {
            "$ref": "#/components/parameters/SourcePartyIdParam"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of transactions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransactionListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl -sS \"https://api.aventraguard.com/v1/transactions?source_party_id=acme-cust-00001\" \\\n  -H \"X-API-Key: ak_live_...\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.aventraguard.com/v1/transactions?source_party_id=acme-cust-00001\",\n    headers={\"X-API-Key\": \"ak_live_...\"},\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"io\"\n    \"net/http\"\n)\n\nreq, _ := http.NewRequest(\"GET\", \"https://api.aventraguard.com/v1/transactions?source_party_id=acme-cust-00001\", nil)\nreq.Header.Set(\"X-API-Key\", \"ak_live_...\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "const resp = await fetch(\"https://api.aventraguard.com/v1/transactions?source_party_id=acme-cust-00001\", {\n  headers: { \"X-API-Key\": \"ak_live_...\" },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$ch = curl_init(\"https://api.aventraguard.com/v1/transactions?source_party_id=acme-cust-00001\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\"X-API-Key: ak_live_...\"],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import java.net.URI;\nimport java.net.http.*;\n\nvar client = HttpClient.newHttpClient();\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/transactions?source_party_id=acme-cust-00001\"))\n    .header(\"X-API-Key\", \"ak_live_...\")\n    .GET()\n    .build();\nHttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/transactions/batch": {
      "post": {
        "tags": [
          "Transactions"
        ],
        "summary": "Bulk-ingest transactions (NDJSON)",
        "description": "Back-fill many transactions in one request as NDJSON (one `POST /v1/transactions` body per line, max 4 MiB). Validated per-row; valid rows enqueued for asynchronous processing, invalid rows recorded as `failed` (without aborting the batch); blank lines ignored. Returns `202` with a `batch_id` + `status_url` — a 202 is NOT confirmation that every row landed; poll `GET /v1/batches/{id}` or await the `batch.completed` webhook. Per-batch `Idempotency-Key` dedups whole chunks. Signing: required (POST). Scope: `transactions:write`.",
        "operationId": "ingestTransactionsBatch",
        "security": [
          {
            "ApiKeyAuth": [],
            "TimestampAuth": [],
            "SignatureAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-ndjson": {
              "schema": {
                "type": "string",
                "description": "NDJSON: one IngestTransactionRequest JSON object per line. Max 4 MiB.",
                "example": "{\"source_transaction_id\":\"t-1\",\"source_party_id\":\"p-1\",\"amount\":\"100.00\",\"currency\":\"CAD\",\"status\":\"completed\",\"method\":\"e_transfer\",\"action\":\"transfer\",\"occurred_at\":\"2026-05-30T13:59:42Z\",\"source_created_at\":\"2026-05-30T13:59:42Z\"}\n"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Batch accepted (valid + invalid rows recorded).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchAccepted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "API_KEY=\"ak_live_...\"\nTS=$(date +%s)\nBODY_HASH=$(openssl dgst -sha256 -hex < transactions.ndjson | awk '{print $2}')\nSIG=$(printf \"%b\" \"POST\\n/v1/transactions/batch\\n${TS}\\n${BODY_HASH}\" | openssl dgst -sha256 -hmac \"$API_KEY\" -hex | awk '{print $2}')\n\ncurl -sS \"https://api.aventraguard.com/v1/transactions/batch\" \\\n  -H \"X-API-Key: $API_KEY\" \\\n  -H \"X-Aventra-Timestamp: $TS\" \\\n  -H \"X-Aventra-Signature: $SIG\" \\\n  -H \"Idempotency-Key: txn-batch-2026-06-01\" \\\n  -H \"Content-Type: application/x-ndjson\" \\\n  --data-binary @transactions.ndjson"
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import hashlib, hmac, time, requests\n\nwith open('transactions.ndjson', 'rb') as f:\n    body = f.read()\nts      = str(int(time.time()))\nbh      = hashlib.sha256(body).hexdigest()\nto_sign = f\"POST\\n/v1/transactions/batch\\n{ts}\\n{bh}\"\nsig     = hmac.new(\"ak_live_...\".encode(), to_sign.encode(), hashlib.sha256).hexdigest()\nresp    = requests.post(\n    \"https://api.aventraguard.com/v1/transactions/batch\",\n    headers={\"X-API-Key\": \"ak_live_...\", \"X-Aventra-Timestamp\": ts,\n             \"X-Aventra-Signature\": sig, \"Idempotency-Key\": \"txn-batch-2026-06-01\",\n             \"Content-Type\": \"application/x-ndjson\"},\n    data=body,\n)\nresp.raise_for_status()\nbatch = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"bytes\"\n    \"crypto/hmac\"\n    \"crypto/sha256\"\n    \"encoding/hex\"\n    \"fmt\"\n    \"io\"\n    \"net/http\"\n    \"os\"\n    \"strconv\"\n    \"time\"\n)\n\nconst apiKey = \"ak_live_...\"\n\nbody, _ := os.ReadFile(\"transactions.ndjson\")\nts := strconv.FormatInt(time.Now().Unix(), 10)\nh := sha256.Sum256(body)\ntoSign := fmt.Sprintf(\"POST\\n/v1/transactions/batch\\n%s\\n%s\", ts, hex.EncodeToString(h[:]))\nmac := hmac.New(sha256.New, []byte(apiKey))\nmac.Write([]byte(toSign))\nsig := hex.EncodeToString(mac.Sum(nil))\n\nreq, _ := http.NewRequest(\"POST\", \"https://api.aventraguard.com/v1/transactions/batch\", bytes.NewReader(body))\nreq.Header.Set(\"X-API-Key\", apiKey)\nreq.Header.Set(\"X-Aventra-Timestamp\", ts)\nreq.Header.Set(\"X-Aventra-Signature\", sig)\nreq.Header.Set(\"Idempotency-Key\", \"txn-batch-2026-06-01\")\nreq.Header.Set(\"Content-Type\", \"application/x-ndjson\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "import { createHash, createHmac } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\n\nconst apiKey = \"ak_live_...\";\nconst body = readFileSync(\"transactions.ndjson\");\nconst ts = String(Math.floor(Date.now() / 1000));\nconst bodyHash = createHash(\"sha256\").update(body).digest(\"hex\");\nconst toSign = `POST\\n/v1/transactions/batch\\n${ts}\\n${bodyHash}`;\nconst sig = createHmac(\"sha256\", apiKey).update(toSign).digest(\"hex\");\n\nconst resp = await fetch(\"https://api.aventraguard.com/v1/transactions/batch\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": apiKey,\n    \"X-Aventra-Timestamp\": ts,\n    \"X-Aventra-Signature\": sig,\n    \"Idempotency-Key\": \"txn-batch-2026-06-01\",\n    \"Content-Type\": \"application/x-ndjson\",\n  },\n  body,\n});\nconst batch = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$key    = \"ak_live_...\";\n$body   = file_get_contents('transactions.ndjson');\n$ts     = (string) time();\n$bh     = hash('sha256', $body);\n$sig    = hash_hmac('sha256', \"POST\\n/v1/transactions/batch\\n$ts\\n$bh\", $key);\n$ch     = curl_init(\"https://api.aventraguard.com/v1/transactions/batch\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POST           => true,\n    CURLOPT_POSTFIELDS     => $body,\n    CURLOPT_HTTPHEADER     => [\n        \"X-API-Key: $key\",\n        \"X-Aventra-Timestamp: $ts\",\n        \"X-Aventra-Signature: $sig\",\n        \"Idempotency-Key: txn-batch-2026-06-01\",\n        \"Content-Type: application/x-ndjson\",\n    ],\n]);\n$batch = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.net.URI; import java.net.http.*;\nimport java.nio.charset.StandardCharsets;\nimport java.nio.file.*;\nimport java.security.MessageDigest;\n// Java 17+ (java.util.HexFormat)\nimport java.time.Instant; import java.util.HexFormat;\n\nString apiKey = \"ak_live_...\";\nbyte[] body   = Files.readAllBytes(Path.of(\"transactions.ndjson\"));\nString ts     = String.valueOf(Instant.now().getEpochSecond());\nString bHash  = HexFormat.of().formatHex(MessageDigest.getInstance(\"SHA-256\").digest(body));\nMac mac = Mac.getInstance(\"HmacSHA256\");\nmac.init(new SecretKeySpec(apiKey.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"));\nString sig = HexFormat.of().formatHex(mac.doFinal(\n    (\"POST\\n/v1/transactions/batch\\n\" + ts + \"\\n\" + bHash).getBytes(StandardCharsets.UTF_8)));\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/transactions/batch\"))\n    .header(\"X-API-Key\", apiKey).header(\"X-Aventra-Timestamp\", ts)\n    .header(\"X-Aventra-Signature\", sig)\n    .header(\"Idempotency-Key\", \"txn-batch-2026-06-01\")\n    .header(\"Content-Type\", \"application/x-ndjson\")\n    .POST(HttpRequest.BodyPublishers.ofByteArray(body))\n    .build();\nHttpResponse<String> resp =\n    HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/transactions/{id}": {
      "get": {
        "tags": [
          "Transactions"
        ],
        "summary": "Get a transaction",
        "description": "Fetch a single transaction by its numeric `id` (returned by ingest/list). RE-scoped: an unknown or other-RE id returns 404. Signing: optional (GET). Scope: `transactions:read`.",
        "operationId": "getTransaction",
        "parameters": [
          {
            "$ref": "#/components/parameters/TransactionIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "The transaction.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Transaction"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl -sS \"https://api.aventraguard.com/v1/transactions/12345\" \\\n  -H \"X-API-Key: ak_live_...\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.aventraguard.com/v1/transactions/12345\",\n    headers={\"X-API-Key\": \"ak_live_...\"},\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"io\"\n    \"net/http\"\n)\n\nreq, _ := http.NewRequest(\"GET\", \"https://api.aventraguard.com/v1/transactions/12345\", nil)\nreq.Header.Set(\"X-API-Key\", \"ak_live_...\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "const resp = await fetch(\"https://api.aventraguard.com/v1/transactions/12345\", {\n  headers: { \"X-API-Key\": \"ak_live_...\" },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$ch = curl_init(\"https://api.aventraguard.com/v1/transactions/12345\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\"X-API-Key: ak_live_...\"],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import java.net.URI;\nimport java.net.http.*;\n\nvar client = HttpClient.newHttpClient();\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/transactions/12345\"))\n    .header(\"X-API-Key\", \"ak_live_...\")\n    .GET()\n    .build();\nHttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/batches/{id}": {
      "get": {
        "tags": [
          "Batches"
        ],
        "summary": "Get batch status",
        "description": "Poll a bulk-ingest batch after the 202. Returns the state (`accepted` -> `processing` -> `completed`, or `cancelled`) and counters. RE-scoped: another RE's batch (or an unknown id) returns 404. Requires the batch's resource read scope (`transactions:read` for a transactions batch, `parties:read` for a parties batch) — enforced after the batch is loaded. Signing: optional (GET).",
        "operationId": "getBatch",
        "parameters": [
          {
            "$ref": "#/components/parameters/BatchIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Batch status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Batch"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl -sS \"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc\" \\\n  -H \"X-API-Key: ak_live_...\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc\",\n    headers={\"X-API-Key\": \"ak_live_...\"},\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"io\"\n    \"net/http\"\n)\n\nreq, _ := http.NewRequest(\"GET\", \"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc\", nil)\nreq.Header.Set(\"X-API-Key\", \"ak_live_...\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "const resp = await fetch(\"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc\", {\n  headers: { \"X-API-Key\": \"ak_live_...\" },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$ch = curl_init(\"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\"X-API-Key: ak_live_...\"],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import java.net.URI;\nimport java.net.http.*;\n\nvar client = HttpClient.newHttpClient();\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc\"))\n    .header(\"X-API-Key\", \"ak_live_...\")\n    .GET()\n    .build();\nHttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/batches/{id}/errors": {
      "get": {
        "tags": [
          "Batches"
        ],
        "summary": "List batch row errors",
        "description": "When `failed_rows > 0`, page the per-row failures (`{line, message}`), offset-paginated by line number. `message` is a PII-free reason (field + message, or a DB SQLSTATE), never the row's value. Same RE-scope + resource read scope as `GET /v1/batches/{id}`. Signing: optional (GET).",
        "operationId": "listBatchErrors",
        "parameters": [
          {
            "$ref": "#/components/parameters/BatchIdPath"
          },
          {
            "$ref": "#/components/parameters/LimitParam"
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of row failures.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchErrorsPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl -sS \"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/errors\" \\\n  -H \"X-API-Key: ak_live_...\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/errors\",\n    headers={\"X-API-Key\": \"ak_live_...\"},\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"io\"\n    \"net/http\"\n)\n\nreq, _ := http.NewRequest(\"GET\", \"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/errors\", nil)\nreq.Header.Set(\"X-API-Key\", \"ak_live_...\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "const resp = await fetch(\"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/errors\", {\n  headers: { \"X-API-Key\": \"ak_live_...\" },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$ch = curl_init(\"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/errors\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\"X-API-Key: ak_live_...\"],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import java.net.URI;\nimport java.net.http.*;\n\nvar client = HttpClient.newHttpClient();\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/errors\"))\n    .header(\"X-API-Key\", \"ak_live_...\")\n    .GET()\n    .build();\nHttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/batches/{id}/cancel": {
      "post": {
        "tags": [
          "Batches"
        ],
        "summary": "Cancel a batch",
        "description": "Stop processing the rest of a batch (e.g. a backfill submitted in error). Already-ingested rows stay ingested; the unprocessed queued remainder is dropped (its row payloads deleted) and the batch moves to `cancelled`. Idempotent: cancelling an already-cancelled batch returns 200 with its status. A completed batch returns 409. RE-scoped; requires the batch's resource write scope (`transactions:write` / `parties:write`). Optional body `{\"reason\": \"...\"}`. Signing: required (POST) — but listed as `optional` in the quick table because the route does not fix a scope at routing time; send a signature for write operations.",
        "operationId": "cancelBatch",
        "security": [
          {
            "ApiKeyAuth": [],
            "TimestampAuth": [],
            "SignatureAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/BatchIdPath"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CancelBatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cancelled (or already cancelled — idempotent). Returns the updated batch status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Batch"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/BatchCompletedConflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "API_KEY=\"ak_live_...\"\nBODY='{\"reason\":\"wrong source file\"}'\nTS=$(date +%s)\nBODY_HASH=$(printf \"%s\" \"$BODY\" | openssl dgst -sha256 -hex | awk '{print $2}')\nSIG=$(printf \"%b\" \"POST\\n/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/cancel\\n${TS}\\n${BODY_HASH}\" | openssl dgst -sha256 -hmac \"$API_KEY\" -hex | awk '{print $2}')\n\ncurl -sS \"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/cancel\" \\\n  -H \"X-API-Key: $API_KEY\" \\\n  -H \"X-Aventra-Timestamp: $TS\" \\\n  -H \"X-Aventra-Signature: $SIG\" \\\n  -H \"Idempotency-Key: cancel-batch-001\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"$BODY\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import hashlib, hmac, time, requests\n\nbody    = '{\"reason\":\"wrong source file\"}'\nts      = str(int(time.time()))\nbh      = hashlib.sha256(body.encode()).hexdigest()\nto_sign = f\"POST\\n/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/cancel\\n{ts}\\n{bh}\"\nsig     = hmac.new(\"ak_live_...\".encode(), to_sign.encode(), hashlib.sha256).hexdigest()\nresp    = requests.post(\n    \"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/cancel\",\n    headers={\"X-API-Key\": \"ak_live_...\", \"X-Aventra-Timestamp\": ts,\n             \"X-Aventra-Signature\": sig, \"Idempotency-Key\": \"cancel-batch-001\",\n             \"Content-Type\": \"application/json\"},\n    data=body,\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"bytes\"\n    \"crypto/hmac\"\n    \"crypto/sha256\"\n    \"encoding/hex\"\n    \"fmt\"\n    \"io\"\n    \"net/http\"\n    \"strconv\"\n    \"time\"\n)\n\nconst apiKey = \"ak_live_...\"\n\nbody := `{\"reason\":\"wrong source file\"}`\nts := strconv.FormatInt(time.Now().Unix(), 10)\nh := sha256.Sum256([]byte(body))\ntoSign := fmt.Sprintf(\"POST\\n/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/cancel\\n%s\\n%s\", ts, hex.EncodeToString(h[:]))\nmac := hmac.New(sha256.New, []byte(apiKey))\nmac.Write([]byte(toSign))\nsig := hex.EncodeToString(mac.Sum(nil))\n\nreq, _ := http.NewRequest(\"POST\", \"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/cancel\", bytes.NewBufferString(body))\nreq.Header.Set(\"X-API-Key\", apiKey)\nreq.Header.Set(\"X-Aventra-Timestamp\", ts)\nreq.Header.Set(\"X-Aventra-Signature\", sig)\nreq.Header.Set(\"Idempotency-Key\", \"cancel-batch-001\")\nreq.Header.Set(\"Content-Type\", \"application/json\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "import { createHash, createHmac } from \"node:crypto\";\n\nconst apiKey = \"ak_live_...\";\nconst body = '{\"reason\":\"wrong source file\"}';\nconst ts = String(Math.floor(Date.now() / 1000));\nconst bodyHash = createHash(\"sha256\").update(body).digest(\"hex\");\nconst toSign = `POST\\n/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/cancel\\n${ts}\\n${bodyHash}`;\nconst sig = createHmac(\"sha256\", apiKey).update(toSign).digest(\"hex\");\n\nconst resp = await fetch(\"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/cancel\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": apiKey,\n    \"X-Aventra-Timestamp\": ts,\n    \"X-Aventra-Signature\": sig,\n    \"Idempotency-Key\": \"cancel-batch-001\",\n    \"Content-Type\": \"application/json\",\n  },\n  body,\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$key    = \"ak_live_...\";\n$body   = '{\"reason\":\"wrong source file\"}';\n$ts     = (string) time();\n$bh     = hash('sha256', $body);\n$sig    = hash_hmac('sha256', \"POST\\n/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/cancel\\n$ts\\n$bh\", $key);\n$ch     = curl_init(\"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/cancel\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POST           => true,\n    CURLOPT_POSTFIELDS     => $body,\n    CURLOPT_HTTPHEADER     => [\n        \"X-API-Key: $key\",\n        \"X-Aventra-Timestamp: $ts\",\n        \"X-Aventra-Signature: $sig\",\n        \"Idempotency-Key: cancel-batch-001\",\n        \"Content-Type: application/json\",\n    ],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.net.URI; import java.net.http.*;\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\n// Java 17+ (java.util.HexFormat)\nimport java.time.Instant; import java.util.HexFormat;\n\nString apiKey = \"ak_live_...\";\nString body   = \"{\\\"reason\\\":\\\"wrong source file\\\"}\";\nString ts     = String.valueOf(Instant.now().getEpochSecond());\nString bHash  = HexFormat.of().formatHex(\n    MessageDigest.getInstance(\"SHA-256\").digest(body.getBytes(StandardCharsets.UTF_8)));\nMac mac = Mac.getInstance(\"HmacSHA256\");\nmac.init(new SecretKeySpec(apiKey.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"));\nString sig = HexFormat.of().formatHex(mac.doFinal(\n    (\"POST\\n/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/cancel\\n\" + ts + \"\\n\" + bHash).getBytes(StandardCharsets.UTF_8)));\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/cancel\"))\n    .header(\"X-API-Key\", apiKey).header(\"X-Aventra-Timestamp\", ts)\n    .header(\"X-Aventra-Signature\", sig)\n    .header(\"Idempotency-Key\", \"cancel-batch-001\")\n    .header(\"Content-Type\", \"application/json\")\n    .method(\"POST\", HttpRequest.BodyPublishers.ofString(body))\n    .build();\nHttpResponse<String> resp =\n    HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/alerts": {
      "get": {
        "tags": [
          "Alerts"
        ],
        "summary": "List alerts",
        "description": "Cursor-paginated list of alerts. Signing: optional (GET). Scope: `alerts:read`.",
        "operationId": "listAlerts",
        "parameters": [
          {
            "$ref": "#/components/parameters/LimitParam"
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          },
          {
            "$ref": "#/components/parameters/OrderParam"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of alerts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlertListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl -sS \"https://api.aventraguard.com/v1/alerts\" \\\n  -H \"X-API-Key: ak_live_...\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.aventraguard.com/v1/alerts\",\n    headers={\"X-API-Key\": \"ak_live_...\"},\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"io\"\n    \"net/http\"\n)\n\nreq, _ := http.NewRequest(\"GET\", \"https://api.aventraguard.com/v1/alerts\", nil)\nreq.Header.Set(\"X-API-Key\", \"ak_live_...\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "const resp = await fetch(\"https://api.aventraguard.com/v1/alerts\", {\n  headers: { \"X-API-Key\": \"ak_live_...\" },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$ch = curl_init(\"https://api.aventraguard.com/v1/alerts\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\"X-API-Key: ak_live_...\"],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import java.net.URI;\nimport java.net.http.*;\n\nvar client = HttpClient.newHttpClient();\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/alerts\"))\n    .header(\"X-API-Key\", \"ak_live_...\")\n    .GET()\n    .build();\nHttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/alerts/{id}": {
      "get": {
        "tags": [
          "Alerts"
        ],
        "summary": "Get an alert",
        "description": "Fetch a single alert by numeric id. RE-scoped: an unknown or other-RE id returns 404. Signing: optional (GET). Scope: `alerts:read`.",
        "operationId": "getAlert",
        "parameters": [
          {
            "$ref": "#/components/parameters/AlertIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "The alert.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Alert"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl -sS \"https://api.aventraguard.com/v1/alerts/33010\" \\\n  -H \"X-API-Key: ak_live_...\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.aventraguard.com/v1/alerts/33010\",\n    headers={\"X-API-Key\": \"ak_live_...\"},\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"io\"\n    \"net/http\"\n)\n\nreq, _ := http.NewRequest(\"GET\", \"https://api.aventraguard.com/v1/alerts/33010\", nil)\nreq.Header.Set(\"X-API-Key\", \"ak_live_...\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "const resp = await fetch(\"https://api.aventraguard.com/v1/alerts/33010\", {\n  headers: { \"X-API-Key\": \"ak_live_...\" },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$ch = curl_init(\"https://api.aventraguard.com/v1/alerts/33010\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\"X-API-Key: ak_live_...\"],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import java.net.URI;\nimport java.net.http.*;\n\nvar client = HttpClient.newHttpClient();\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/alerts/33010\"))\n    .header(\"X-API-Key\", \"ak_live_...\")\n    .GET()\n    .build();\nHttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/alerts/{id}/disposition": {
      "patch": {
        "tags": [
          "Alerts"
        ],
        "summary": "Disposition an alert",
        "description": "Mark an alert as a confirmed finding (`true_positive` -> alert becomes `escalated`) or a false positive (`false_positive` -> alert becomes `dismissed`). An alert already in a closed status cannot be dispositioned (400). Signing: required (PATCH). Idempotency-Key: supported. Scope: `alerts:write`.",
        "operationId": "dispositionAlert",
        "security": [
          {
            "ApiKeyAuth": [],
            "TimestampAuth": [],
            "SignatureAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/AlertIdPath"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DispositionRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Disposition applied. Returns the updated alert.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Alert"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "API_KEY=\"ak_live_...\"\nBODY='{\"disposition\":\"true_positive\",\"notes\":\"Confirmed structuring pattern\"}'\nTS=$(date +%s)\nBODY_HASH=$(printf \"%s\" \"$BODY\" | openssl dgst -sha256 -hex | awk '{print $2}')\nSIG=$(printf \"%b\" \"PATCH\\n/v1/alerts/33010/disposition\\n${TS}\\n${BODY_HASH}\" | openssl dgst -sha256 -hmac \"$API_KEY\" -hex | awk '{print $2}')\n\ncurl -sS -X PATCH \"https://api.aventraguard.com/v1/alerts/33010/disposition\" \\\n  -H \"X-API-Key: $API_KEY\" \\\n  -H \"X-Aventra-Timestamp: $TS\" \\\n  -H \"X-Aventra-Signature: $SIG\" \\\n  -H \"Idempotency-Key: dispo-33010\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"$BODY\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import hashlib, hmac, time, requests\n\nbody    = '{\"disposition\":\"true_positive\",\"notes\":\"Confirmed structuring pattern\"}'\nts      = str(int(time.time()))\nbh      = hashlib.sha256(body.encode()).hexdigest()\nto_sign = f\"PATCH\\n/v1/alerts/33010/disposition\\n{ts}\\n{bh}\"\nsig     = hmac.new(\"ak_live_...\".encode(), to_sign.encode(), hashlib.sha256).hexdigest()\nresp    = requests.patch(\n    \"https://api.aventraguard.com/v1/alerts/33010/disposition\",\n    headers={\"X-API-Key\": \"ak_live_...\", \"X-Aventra-Timestamp\": ts,\n             \"X-Aventra-Signature\": sig, \"Idempotency-Key\": \"dispo-33010\",\n             \"Content-Type\": \"application/json\"},\n    data=body,\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"bytes\"\n    \"crypto/hmac\"\n    \"crypto/sha256\"\n    \"encoding/hex\"\n    \"fmt\"\n    \"io\"\n    \"net/http\"\n    \"strconv\"\n    \"time\"\n)\n\nconst apiKey = \"ak_live_...\"\n\nbody := `{\"disposition\":\"true_positive\",\"notes\":\"Confirmed structuring pattern\"}`\nts := strconv.FormatInt(time.Now().Unix(), 10)\nh := sha256.Sum256([]byte(body))\ntoSign := fmt.Sprintf(\"PATCH\\n/v1/alerts/33010/disposition\\n%s\\n%s\", ts, hex.EncodeToString(h[:]))\nmac := hmac.New(sha256.New, []byte(apiKey))\nmac.Write([]byte(toSign))\nsig := hex.EncodeToString(mac.Sum(nil))\n\nreq, _ := http.NewRequest(\"PATCH\", \"https://api.aventraguard.com/v1/alerts/33010/disposition\", bytes.NewBufferString(body))\nreq.Header.Set(\"X-API-Key\", apiKey)\nreq.Header.Set(\"X-Aventra-Timestamp\", ts)\nreq.Header.Set(\"X-Aventra-Signature\", sig)\nreq.Header.Set(\"Idempotency-Key\", \"dispo-33010\")\nreq.Header.Set(\"Content-Type\", \"application/json\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "import { createHash, createHmac } from \"node:crypto\";\n\nconst apiKey = \"ak_live_...\";\nconst body = '{\"disposition\":\"true_positive\",\"notes\":\"Confirmed structuring pattern\"}';\nconst ts = String(Math.floor(Date.now() / 1000));\nconst bodyHash = createHash(\"sha256\").update(body).digest(\"hex\");\nconst toSign = `PATCH\\n/v1/alerts/33010/disposition\\n${ts}\\n${bodyHash}`;\nconst sig = createHmac(\"sha256\", apiKey).update(toSign).digest(\"hex\");\n\nconst resp = await fetch(\"https://api.aventraguard.com/v1/alerts/33010/disposition\", {\n  method: \"PATCH\",\n  headers: {\n    \"X-API-Key\": apiKey,\n    \"X-Aventra-Timestamp\": ts,\n    \"X-Aventra-Signature\": sig,\n    \"Idempotency-Key\": \"dispo-33010\",\n    \"Content-Type\": \"application/json\",\n  },\n  body,\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$key    = \"ak_live_...\";\n$body   = '{\"disposition\":\"true_positive\",\"notes\":\"Confirmed structuring pattern\"}';\n$ts     = (string) time();\n$bh     = hash('sha256', $body);\n$sig    = hash_hmac('sha256', \"PATCH\\n/v1/alerts/33010/disposition\\n$ts\\n$bh\", $key);\n$ch     = curl_init(\"https://api.aventraguard.com/v1/alerts/33010/disposition\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_CUSTOMREQUEST  => 'PATCH',\n    CURLOPT_POSTFIELDS     => $body,\n    CURLOPT_HTTPHEADER     => [\n        \"X-API-Key: $key\",\n        \"X-Aventra-Timestamp: $ts\",\n        \"X-Aventra-Signature: $sig\",\n        \"Idempotency-Key: dispo-33010\",\n        \"Content-Type: application/json\",\n    ],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.net.URI; import java.net.http.*;\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\n// Java 17+ (java.util.HexFormat)\nimport java.time.Instant; import java.util.HexFormat;\n\nString apiKey = \"ak_live_...\";\nString body   = \"{\\\"disposition\\\":\\\"true_positive\\\",\\\"notes\\\":\\\"Confirmed structuring pattern\\\"}\";\nString ts     = String.valueOf(Instant.now().getEpochSecond());\nString bHash  = HexFormat.of().formatHex(\n    MessageDigest.getInstance(\"SHA-256\").digest(body.getBytes(StandardCharsets.UTF_8)));\nMac mac = Mac.getInstance(\"HmacSHA256\");\nmac.init(new SecretKeySpec(apiKey.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"));\nString sig = HexFormat.of().formatHex(mac.doFinal(\n    (\"PATCH\\n/v1/alerts/33010/disposition\\n\" + ts + \"\\n\" + bHash).getBytes(StandardCharsets.UTF_8)));\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/alerts/33010/disposition\"))\n    .header(\"X-API-Key\", apiKey).header(\"X-Aventra-Timestamp\", ts)\n    .header(\"X-Aventra-Signature\", sig)\n    .header(\"Idempotency-Key\", \"dispo-33010\")\n    .header(\"Content-Type\", \"application/json\")\n    .method(\"PATCH\", HttpRequest.BodyPublishers.ofString(body))\n    .build();\nHttpResponse<String> resp =\n    HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/cases": {
      "get": {
        "tags": [
          "Cases"
        ],
        "summary": "List cases",
        "description": "Cursor-paginated list of AML investigation cases. Signing: optional (GET). Scope: `cases:read`.",
        "operationId": "listCases",
        "parameters": [
          {
            "$ref": "#/components/parameters/LimitParam"
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          },
          {
            "$ref": "#/components/parameters/OrderParam"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of cases.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaseListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl -sS \"https://api.aventraguard.com/v1/cases\" \\\n  -H \"X-API-Key: ak_live_...\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.aventraguard.com/v1/cases\",\n    headers={\"X-API-Key\": \"ak_live_...\"},\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"io\"\n    \"net/http\"\n)\n\nreq, _ := http.NewRequest(\"GET\", \"https://api.aventraguard.com/v1/cases\", nil)\nreq.Header.Set(\"X-API-Key\", \"ak_live_...\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "const resp = await fetch(\"https://api.aventraguard.com/v1/cases\", {\n  headers: { \"X-API-Key\": \"ak_live_...\" },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$ch = curl_init(\"https://api.aventraguard.com/v1/cases\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\"X-API-Key: ak_live_...\"],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import java.net.URI;\nimport java.net.http.*;\n\nvar client = HttpClient.newHttpClient();\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/cases\"))\n    .header(\"X-API-Key\", \"ak_live_...\")\n    .GET()\n    .build();\nHttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/cases/{id}": {
      "get": {
        "tags": [
          "Cases"
        ],
        "summary": "Get a case",
        "description": "Fetch a single case by numeric id. RE-scoped: an unknown or other-RE id returns 404. Signing: optional (GET). Scope: `cases:read`.",
        "operationId": "getCase",
        "parameters": [
          {
            "$ref": "#/components/parameters/CaseIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "The case.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaseDetail"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl -sS \"https://api.aventraguard.com/v1/cases/1502\" \\\n  -H \"X-API-Key: ak_live_...\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.aventraguard.com/v1/cases/1502\",\n    headers={\"X-API-Key\": \"ak_live_...\"},\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"io\"\n    \"net/http\"\n)\n\nreq, _ := http.NewRequest(\"GET\", \"https://api.aventraguard.com/v1/cases/1502\", nil)\nreq.Header.Set(\"X-API-Key\", \"ak_live_...\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "const resp = await fetch(\"https://api.aventraguard.com/v1/cases/1502\", {\n  headers: { \"X-API-Key\": \"ak_live_...\" },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$ch = curl_init(\"https://api.aventraguard.com/v1/cases/1502\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\"X-API-Key: ak_live_...\"],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import java.net.URI;\nimport java.net.http.*;\n\nvar client = HttpClient.newHttpClient();\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/cases/1502\"))\n    .header(\"X-API-Key\", \"ak_live_...\")\n    .GET()\n    .build();\nHttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/filings": {
      "get": {
        "tags": [
          "Filings"
        ],
        "summary": "List filings",
        "description": "Cursor-paginated, read-only list of STR filings. The narrative and F2R XML are deliberately omitted from the public API. Signing: optional (GET). Scope: `filings:read`.",
        "operationId": "listFilings",
        "parameters": [
          {
            "$ref": "#/components/parameters/LimitParam"
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          },
          {
            "$ref": "#/components/parameters/OrderParam"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of filings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FilingListResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl -sS \"https://api.aventraguard.com/v1/filings\" \\\n  -H \"X-API-Key: ak_live_...\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.aventraguard.com/v1/filings\",\n    headers={\"X-API-Key\": \"ak_live_...\"},\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"io\"\n    \"net/http\"\n)\n\nreq, _ := http.NewRequest(\"GET\", \"https://api.aventraguard.com/v1/filings\", nil)\nreq.Header.Set(\"X-API-Key\", \"ak_live_...\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "const resp = await fetch(\"https://api.aventraguard.com/v1/filings\", {\n  headers: { \"X-API-Key\": \"ak_live_...\" },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$ch = curl_init(\"https://api.aventraguard.com/v1/filings\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\"X-API-Key: ak_live_...\"],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import java.net.URI;\nimport java.net.http.*;\n\nvar client = HttpClient.newHttpClient();\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/filings\"))\n    .header(\"X-API-Key\", \"ak_live_...\")\n    .GET()\n    .build();\nHttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/filings/{id}": {
      "get": {
        "tags": [
          "Filings"
        ],
        "summary": "Get a filing",
        "description": "Fetch a single STR filing by numeric id. RE-scoped: an unknown or other-RE id returns 404. The narrative and F2R XML are omitted by design. Signing: optional (GET). Scope: `filings:read`.",
        "operationId": "getFiling",
        "parameters": [
          {
            "$ref": "#/components/parameters/FilingIdPath"
          }
        ],
        "responses": {
          "200": {
            "description": "The filing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FilingDetail"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl -sS \"https://api.aventraguard.com/v1/filings/2204\" \\\n  -H \"X-API-Key: ak_live_...\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.aventraguard.com/v1/filings/2204\",\n    headers={\"X-API-Key\": \"ak_live_...\"},\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"io\"\n    \"net/http\"\n)\n\nreq, _ := http.NewRequest(\"GET\", \"https://api.aventraguard.com/v1/filings/2204\", nil)\nreq.Header.Set(\"X-API-Key\", \"ak_live_...\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "const resp = await fetch(\"https://api.aventraguard.com/v1/filings/2204\", {\n  headers: { \"X-API-Key\": \"ak_live_...\" },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$ch = curl_init(\"https://api.aventraguard.com/v1/filings/2204\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\"X-API-Key: ak_live_...\"],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import java.net.URI;\nimport java.net.http.*;\n\nvar client = HttpClient.newHttpClient();\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/filings/2204\"))\n    .header(\"X-API-Key\", \"ak_live_...\")\n    .GET()\n    .build();\nHttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhooks",
        "description": "List your webhook registrations. The signing `secret` is never returned here (only on creation). Signing: optional (GET). Scope: `webhooks:read`.",
        "operationId": "listWebhooks",
        "responses": {
          "200": {
            "description": "Your webhook registrations.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookListResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl -sS \"https://api.aventraguard.com/v1/webhooks\" \\\n  -H \"X-API-Key: ak_live_...\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.aventraguard.com/v1/webhooks\",\n    headers={\"X-API-Key\": \"ak_live_...\"},\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"io\"\n    \"net/http\"\n)\n\nreq, _ := http.NewRequest(\"GET\", \"https://api.aventraguard.com/v1/webhooks\", nil)\nreq.Header.Set(\"X-API-Key\", \"ak_live_...\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "const resp = await fetch(\"https://api.aventraguard.com/v1/webhooks\", {\n  headers: { \"X-API-Key\": \"ak_live_...\" },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$ch = curl_init(\"https://api.aventraguard.com/v1/webhooks\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\"X-API-Key: ak_live_...\"],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import java.net.URI;\nimport java.net.http.*;\n\nvar client = HttpClient.newHttpClient();\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/webhooks\"))\n    .header(\"X-API-Key\", \"ak_live_...\")\n    .GET()\n    .build();\nHttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Register a webhook",
        "description": "Register a webhook receiver. The 201 response includes the signing `secret` exactly once — capture it immediately, it is not returned on later GETs. The `url` must be a public HTTPS URL (http:// and private/loopback hosts are rejected). An empty/omitted `event_filters` array means deliver everything. Event types: `alert.created`, `alert.disposed`, `case.opened`, `case.closed`, `filing.submitted`, `party.screening_hit`, `party.screening_hit.created`, `party.risk_tier_changed`, `batch.completed`. Signing: required (POST). Idempotency-Key: supported. Scope: `webhooks:write`.",
        "operationId": "createWebhook",
        "security": [
          {
            "ApiKeyAuth": [],
            "TimestampAuth": [],
            "SignatureAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook created. The response includes the signing `secret` exactly once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookWithSecret"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyConflict"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "API_KEY=\"ak_live_...\"\nBODY='{\"url\":\"https://hooks.acme.example.com/aventraguard\",\"label\":\"acme-prod\"}'\nTS=$(date +%s)\nBODY_HASH=$(printf \"%s\" \"$BODY\" | openssl dgst -sha256 -hex | awk '{print $2}')\nSIG=$(printf \"%b\" \"POST\\n/v1/webhooks\\n${TS}\\n${BODY_HASH}\" | openssl dgst -sha256 -hmac \"$API_KEY\" -hex | awk '{print $2}')\n\ncurl -sS \"https://api.aventraguard.com/v1/webhooks\" \\\n  -H \"X-API-Key: $API_KEY\" \\\n  -H \"X-Aventra-Timestamp: $TS\" \\\n  -H \"X-Aventra-Signature: $SIG\" \\\n  -H \"Idempotency-Key: webhook-acme-prod\" \\\n  -H \"Content-Type: application/json\" \\\n  -d \"$BODY\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import hashlib, hmac, time, requests\n\nbody    = '{\"url\":\"https://hooks.acme.example.com/aventraguard\",\"label\":\"acme-prod\"}'\nts      = str(int(time.time()))\nbh      = hashlib.sha256(body.encode()).hexdigest()\nto_sign = f\"POST\\n/v1/webhooks\\n{ts}\\n{bh}\"\nsig     = hmac.new(\"ak_live_...\".encode(), to_sign.encode(), hashlib.sha256).hexdigest()\nresp    = requests.post(\n    \"https://api.aventraguard.com/v1/webhooks\",\n    headers={\"X-API-Key\": \"ak_live_...\", \"X-Aventra-Timestamp\": ts,\n             \"X-Aventra-Signature\": sig, \"Idempotency-Key\": \"webhook-acme-prod\",\n             \"Content-Type\": \"application/json\"},\n    data=body,\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"bytes\"\n    \"crypto/hmac\"\n    \"crypto/sha256\"\n    \"encoding/hex\"\n    \"fmt\"\n    \"io\"\n    \"net/http\"\n    \"strconv\"\n    \"time\"\n)\n\nconst apiKey = \"ak_live_...\"\n\nbody := `{\"url\":\"https://hooks.acme.example.com/aventraguard\",\"label\":\"acme-prod\"}`\nts := strconv.FormatInt(time.Now().Unix(), 10)\nh := sha256.Sum256([]byte(body))\ntoSign := fmt.Sprintf(\"POST\\n/v1/webhooks\\n%s\\n%s\", ts, hex.EncodeToString(h[:]))\nmac := hmac.New(sha256.New, []byte(apiKey))\nmac.Write([]byte(toSign))\nsig := hex.EncodeToString(mac.Sum(nil))\n\nreq, _ := http.NewRequest(\"POST\", \"https://api.aventraguard.com/v1/webhooks\", bytes.NewBufferString(body))\nreq.Header.Set(\"X-API-Key\", apiKey)\nreq.Header.Set(\"X-Aventra-Timestamp\", ts)\nreq.Header.Set(\"X-Aventra-Signature\", sig)\nreq.Header.Set(\"Idempotency-Key\", \"webhook-acme-prod\")\nreq.Header.Set(\"Content-Type\", \"application/json\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "import { createHash, createHmac } from \"node:crypto\";\n\nconst apiKey = \"ak_live_...\";\nconst body = '{\"url\":\"https://hooks.acme.example.com/aventraguard\",\"label\":\"acme-prod\"}';\nconst ts = String(Math.floor(Date.now() / 1000));\nconst bodyHash = createHash(\"sha256\").update(body).digest(\"hex\");\nconst toSign = `POST\\n/v1/webhooks\\n${ts}\\n${bodyHash}`;\nconst sig = createHmac(\"sha256\", apiKey).update(toSign).digest(\"hex\");\n\nconst resp = await fetch(\"https://api.aventraguard.com/v1/webhooks\", {\n  method: \"POST\",\n  headers: {\n    \"X-API-Key\": apiKey,\n    \"X-Aventra-Timestamp\": ts,\n    \"X-Aventra-Signature\": sig,\n    \"Idempotency-Key\": \"webhook-acme-prod\",\n    \"Content-Type\": \"application/json\",\n  },\n  body,\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$key    = \"ak_live_...\";\n$body   = '{\"url\":\"https://hooks.acme.example.com/aventraguard\",\"label\":\"acme-prod\"}';\n$ts     = (string) time();\n$bh     = hash('sha256', $body);\n$sig    = hash_hmac('sha256', \"POST\\n/v1/webhooks\\n$ts\\n$bh\", $key);\n$ch     = curl_init(\"https://api.aventraguard.com/v1/webhooks\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_POST           => true,\n    CURLOPT_POSTFIELDS     => $body,\n    CURLOPT_HTTPHEADER     => [\n        \"X-API-Key: $key\",\n        \"X-Aventra-Timestamp: $ts\",\n        \"X-Aventra-Signature: $sig\",\n        \"Idempotency-Key: webhook-acme-prod\",\n        \"Content-Type: application/json\",\n    ],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.net.URI; import java.net.http.*;\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\n// Java 17+ (java.util.HexFormat)\nimport java.time.Instant; import java.util.HexFormat;\n\nString apiKey = \"ak_live_...\";\nString body   = \"{\\\"url\\\":\\\"https://hooks.acme.example.com/aventraguard\\\",\\\"label\\\":\\\"acme-prod\\\"}\";\nString ts     = String.valueOf(Instant.now().getEpochSecond());\nString bHash  = HexFormat.of().formatHex(\n    MessageDigest.getInstance(\"SHA-256\").digest(body.getBytes(StandardCharsets.UTF_8)));\nMac mac = Mac.getInstance(\"HmacSHA256\");\nmac.init(new SecretKeySpec(apiKey.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"));\nString sig = HexFormat.of().formatHex(mac.doFinal(\n    (\"POST\\n/v1/webhooks\\n\" + ts + \"\\n\" + bHash).getBytes(StandardCharsets.UTF_8)));\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/webhooks\"))\n    .header(\"X-API-Key\", apiKey).header(\"X-Aventra-Timestamp\", ts)\n    .header(\"X-Aventra-Signature\", sig)\n    .header(\"Idempotency-Key\", \"webhook-acme-prod\")\n    .header(\"Content-Type\", \"application/json\")\n    .method(\"POST\", HttpRequest.BodyPublishers.ofString(body))\n    .build();\nHttpResponse<String> resp =\n    HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/webhooks/{id}": {
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete (revoke) a webhook",
        "description": "Marks the webhook revoked. In-flight deliveries already enqueued still complete their retry schedule; no new events are queued after the revoke. Idempotent by nature (re-deleting returns 404). Signing: required (DELETE). Scope: `webhooks:write`.",
        "operationId": "deleteWebhook",
        "security": [
          {
            "ApiKeyAuth": [],
            "TimestampAuth": [],
            "SignatureAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/WebhookIdPath"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted (revoked). Empty body."
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "API_KEY=\"ak_live_...\"\nTS=$(date +%s)\nBODY_HASH=$(printf \"%s\" \"\" | openssl dgst -sha256 -hex | awk '{print $2}')\nSIG=$(printf \"%b\" \"DELETE\\n/v1/webhooks/0193b6c0-d1e2-7a00-bf01-1234567890ab\\n${TS}\\n${BODY_HASH}\" | openssl dgst -sha256 -hmac \"$API_KEY\" -hex | awk '{print $2}')\n\ncurl -sS -X DELETE \"https://api.aventraguard.com/v1/webhooks/0193b6c0-d1e2-7a00-bf01-1234567890ab\" \\\n  -H \"X-API-Key: $API_KEY\" \\\n  -H \"X-Aventra-Timestamp: $TS\" \\\n  -H \"X-Aventra-Signature: $SIG\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import hashlib, hmac, time, requests\n\nbody    = \"\"\nts      = str(int(time.time()))\nbh      = hashlib.sha256(body.encode()).hexdigest()\nto_sign = f\"DELETE\\n/v1/webhooks/0193b6c0-d1e2-7a00-bf01-1234567890ab\\n{ts}\\n{bh}\"\nsig     = hmac.new(\"ak_live_...\".encode(), to_sign.encode(), hashlib.sha256).hexdigest()\nresp    = requests.delete(\n    \"https://api.aventraguard.com/v1/webhooks/0193b6c0-d1e2-7a00-bf01-1234567890ab\",\n    headers={\"X-API-Key\": \"ak_live_...\", \"X-Aventra-Timestamp\": ts,\n             \"X-Aventra-Signature\": sig},\n)\nresp.raise_for_status()\n# 204 No Content on success"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"crypto/hmac\"\n    \"crypto/sha256\"\n    \"encoding/hex\"\n    \"fmt\"\n    \"io\"\n    \"net/http\"\n    \"strconv\"\n    \"time\"\n)\n\nconst apiKey = \"ak_live_...\"\n\nvar body string\nts := strconv.FormatInt(time.Now().Unix(), 10)\nh := sha256.Sum256([]byte(body))\ntoSign := fmt.Sprintf(\"DELETE\\n/v1/webhooks/0193b6c0-d1e2-7a00-bf01-1234567890ab\\n%s\\n%s\", ts, hex.EncodeToString(h[:]))\nmac := hmac.New(sha256.New, []byte(apiKey))\nmac.Write([]byte(toSign))\nsig := hex.EncodeToString(mac.Sum(nil))\n\nreq, _ := http.NewRequest(\"DELETE\", \"https://api.aventraguard.com/v1/webhooks/0193b6c0-d1e2-7a00-bf01-1234567890ab\", nil)\nreq.Header.Set(\"X-API-Key\", apiKey)\nreq.Header.Set(\"X-Aventra-Timestamp\", ts)\nreq.Header.Set(\"X-Aventra-Signature\", sig)\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "import { createHash, createHmac } from \"node:crypto\";\n\nconst apiKey = \"ak_live_...\";\nconst body = '';\nconst ts = String(Math.floor(Date.now() / 1000));\nconst bodyHash = createHash(\"sha256\").update(body).digest(\"hex\");\nconst toSign = `DELETE\\n/v1/webhooks/0193b6c0-d1e2-7a00-bf01-1234567890ab\\n${ts}\\n${bodyHash}`;\nconst sig = createHmac(\"sha256\", apiKey).update(toSign).digest(\"hex\");\n\nconst resp = await fetch(\"https://api.aventraguard.com/v1/webhooks/0193b6c0-d1e2-7a00-bf01-1234567890ab\", {\n  method: \"DELETE\",\n  headers: {\n    \"X-API-Key\": apiKey,\n    \"X-Aventra-Timestamp\": ts,\n    \"X-Aventra-Signature\": sig,\n  },\n});\n// 204 No Content on success"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$key    = \"ak_live_...\";\n$body   = \"\";\n$ts     = (string) time();\n$bh     = hash('sha256', $body);\n$sig    = hash_hmac('sha256', \"DELETE\\n/v1/webhooks/0193b6c0-d1e2-7a00-bf01-1234567890ab\\n$ts\\n$bh\", $key);\n$ch     = curl_init(\"https://api.aventraguard.com/v1/webhooks/0193b6c0-d1e2-7a00-bf01-1234567890ab\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_CUSTOMREQUEST  => 'DELETE',\n    CURLOPT_HTTPHEADER     => [\n        \"X-API-Key: $key\",\n        \"X-Aventra-Timestamp: $ts\",\n        \"X-Aventra-Signature: $sig\",\n    ],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\nimport java.net.URI; import java.net.http.*;\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\n// Java 17+ (java.util.HexFormat)\nimport java.time.Instant; import java.util.HexFormat;\n\nString apiKey = \"ak_live_...\";\nString body   = \"\";\nString ts     = String.valueOf(Instant.now().getEpochSecond());\nString bHash  = HexFormat.of().formatHex(\n    MessageDigest.getInstance(\"SHA-256\").digest(body.getBytes(StandardCharsets.UTF_8)));\nMac mac = Mac.getInstance(\"HmacSHA256\");\nmac.init(new SecretKeySpec(apiKey.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"));\nString sig = HexFormat.of().formatHex(mac.doFinal(\n    (\"DELETE\\n/v1/webhooks/0193b6c0-d1e2-7a00-bf01-1234567890ab\\n\" + ts + \"\\n\" + bHash).getBytes(StandardCharsets.UTF_8)));\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/webhooks/0193b6c0-d1e2-7a00-bf01-1234567890ab\"))\n    .header(\"X-API-Key\", apiKey).header(\"X-Aventra-Timestamp\", ts)\n    .header(\"X-Aventra-Signature\", sig)\n    .DELETE()\n    .build();\nHttpResponse<String> resp =\n    HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    },
    "/v1/audit-log": {
      "get": {
        "tags": [
          "Audit Log"
        ],
        "summary": "Read the audit log",
        "description": "Read-only, allow-listed, RE-scoped view of the WORM audit trail (compliance field-set proposal, MLRO-approved 2026-06-07). Default-deny: only allow-listed `action_type` values are ever returned; excluded entries are silently dropped (no redacted placeholders). Actor identity is masked to an `actor_class` enum (raw UUIDs never returned); `before_hash`/`after_hash`/`row_hash`/`prev_hash`/`re_id` are never returned. 90-day maximum lookback. Requires the `audit:read` scope, which is NOT in the default scope set and ships dark — granted per credential on explicit request (contact the operator). Signing: optional (GET).",
        "operationId": "listAuditLog",
        "parameters": [
          {
            "$ref": "#/components/parameters/AuditActionTypeParam"
          },
          {
            "$ref": "#/components/parameters/AuditSubjectTypeParam"
          },
          {
            "$ref": "#/components/parameters/AuditSubjectIdParam"
          },
          {
            "$ref": "#/components/parameters/AuditFromParam"
          },
          {
            "$ref": "#/components/parameters/AuditToParam"
          },
          {
            "$ref": "#/components/parameters/CursorParam"
          },
          {
            "$ref": "#/components/parameters/AuditLimitParam"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of audit-log entries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuditLogResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/InsufficientScope"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        },
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "cURL",
            "source": "curl -sS \"https://api.aventraguard.com/v1/audit-log?action_type=BATCH_INGEST_COMPLETED\" \\\n  -H \"X-API-Key: ak_live_...\""
          },
          {
            "lang": "Python",
            "label": "Python",
            "source": "import requests\n\nresp = requests.get(\n    \"https://api.aventraguard.com/v1/audit-log?action_type=BATCH_INGEST_COMPLETED\",\n    headers={\"X-API-Key\": \"ak_live_...\"},\n)\nresp.raise_for_status()\ndata = resp.json()"
          },
          {
            "lang": "Go",
            "label": "Go",
            "source": "import (\n    \"io\"\n    \"net/http\"\n)\n\nreq, _ := http.NewRequest(\"GET\", \"https://api.aventraguard.com/v1/audit-log?action_type=BATCH_INGEST_COMPLETED\", nil)\nreq.Header.Set(\"X-API-Key\", \"ak_live_...\")\nresp, _ := http.DefaultClient.Do(req)\ndefer resp.Body.Close()\ndata, _ := io.ReadAll(resp.Body)"
          },
          {
            "lang": "TypeScript",
            "label": "TypeScript",
            "source": "const resp = await fetch(\"https://api.aventraguard.com/v1/audit-log?action_type=BATCH_INGEST_COMPLETED\", {\n  headers: { \"X-API-Key\": \"ak_live_...\" },\n});\nconst data = await resp.json();"
          },
          {
            "lang": "PHP",
            "label": "PHP",
            "source": "<?php\n$ch = curl_init(\"https://api.aventraguard.com/v1/audit-log?action_type=BATCH_INGEST_COMPLETED\");\ncurl_setopt_array($ch, [\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\"X-API-Key: ak_live_...\"],\n]);\n$data = json_decode(curl_exec($ch), true);\ncurl_close($ch);"
          },
          {
            "lang": "Java",
            "label": "Java",
            "source": "import java.net.URI;\nimport java.net.http.*;\n\nvar client = HttpClient.newHttpClient();\nvar req = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.aventraguard.com/v1/audit-log?action_type=BATCH_INGEST_COMPLETED\"))\n    .header(\"X-API-Key\", \"ak_live_...\")\n    .GET()\n    .build();\nHttpResponse<String> resp = client.send(req, HttpResponse.BodyHandlers.ofString());"
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Your API key. `ak_live_*` on production, `ak_test_*` on staging/dev (the host and prefix must agree). Required on EVERY request, including GETs. The raw key is also the HMAC key used to sign writes."
      },
      "TimestampAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Aventra-Timestamp",
        "description": "Unix epoch seconds (string of decimal digits) used in the signed string. Required on writes (POST/PATCH/DELETE); optional on GET. Must be within 5 minutes of server time and must match the timestamp inside the HMAC byte-for-byte."
      },
      "SignatureAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Aventra-Signature",
        "description": "Lowercase-hex HMAC-SHA256 signature (64 chars). string-to-sign = `METHOD\\nPATH_WITH_QUERY\\nTIMESTAMP\\nBODY_SHA256_HEX` (literal newlines, no trailing newline); the HMAC key is the raw API key; `BODY_SHA256_HEX` of an empty body is the well-known `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`. Required on writes; optional on GET (but if you send a signature on a GET you must also send a valid timestamp — partial signing is rejected). Writes should also carry an `Idempotency-Key` header for safe retry."
      }
    },
    "parameters": {
      "LimitParam": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Page size, 1-200 inclusive. Default 50. Out-of-range returns 400.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      },
      "CursorParam": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "Opaque cursor from a previous response's `next_cursor`. A malformed/negative cursor returns 400.",
        "schema": {
          "type": "string"
        }
      },
      "OrderParam": {
        "name": "order",
        "in": "query",
        "required": false,
        "description": "Sort order.",
        "schema": {
          "type": "string",
          "enum": [
            "asc",
            "desc"
          ],
          "default": "desc"
        }
      },
      "SinceParam": {
        "name": "since",
        "in": "query",
        "required": false,
        "description": "RFC 3339 timestamp. Only hits first seen at/after this instant (incremental sync).",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "SourcePartyIdParam": {
        "name": "source_party_id",
        "in": "query",
        "required": false,
        "description": "Filter to one party's transactions by your source_party_id (<=255 chars).",
        "schema": {
          "type": "string",
          "maxLength": 255
        }
      },
      "IdempotencyKeyHeader": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Unique key (UUID v4 recommended), max 128 bytes, scoped per API key. On POST/PATCH the first 2xx response is cached 24h and replayed (with `Idempotency-Replayed: true`) for repeat keys; reusing a key with a different body returns 409. Ignored on GET; DELETE is idempotent by nature.",
        "schema": {
          "type": "string",
          "maxLength": 128
        }
      },
      "PartyIdPath": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Numeric AventraGuard party ID (not your source_party_id). Must be a positive integer.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9]+$"
        }
      },
      "TransactionIdPath": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Numeric transaction id returned by ingest/list. Must be a positive integer.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9]+$"
        }
      },
      "AlertIdPath": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Numeric alert id. Must be a positive integer.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9]+$"
        }
      },
      "CaseIdPath": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Numeric case id. Must be a positive integer.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9]+$"
        }
      },
      "FilingIdPath": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Numeric filing id. Must be a positive integer.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9]+$"
        }
      },
      "BatchIdPath": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Batch UUID returned by a bulk-ingest 202.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "WebhookIdPath": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Webhook registration UUID.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "AuditActionTypeParam": {
        "name": "action_type",
        "in": "query",
        "required": false,
        "description": "Repeatable. Filter by allow-listed audit action type(s). A value not on the allow-list returns 400 (it is not silently treated as empty).",
        "schema": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "style": "form",
        "explode": true
      },
      "AuditSubjectTypeParam": {
        "name": "subject_type",
        "in": "query",
        "required": false,
        "description": "Free-text filter on the entity class (e.g. rule, integration, transaction).",
        "schema": {
          "type": "string"
        }
      },
      "AuditSubjectIdParam": {
        "name": "subject_id",
        "in": "query",
        "required": false,
        "description": "Free-text filter on the entity primary key.",
        "schema": {
          "type": "string"
        }
      },
      "AuditFromParam": {
        "name": "from",
        "in": "query",
        "required": false,
        "description": "RFC 3339 datetime, inclusive. Must be <= `to`. Maximum 90-day lookback.",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "AuditToParam": {
        "name": "to",
        "in": "query",
        "required": false,
        "description": "RFC 3339 datetime, inclusive. Must be >= `from`.",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "AuditLimitParam": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Page size for the audit log, 1-100 inclusive. Default 50.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 50
        }
      }
    },
    "responses": {
      "InvalidRequest": {
        "description": "400 — body cannot be parsed, a URL segment is malformed, an out-of-range pagination param, or an alert in a status that cannot be dispositioned.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://aventraguard.com/errors/invalid-request",
              "title": "Invalid request",
              "status": 400,
              "detail": "Request body must be valid JSON.",
              "instance": "/v1/parties"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "401 — authentication failed. One of: missing-api-key, invalid-api-key, signature-mismatch, timestamp-expired, replay-detected. Authentication is evaluated before routing/scope.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://aventraguard.com/errors/signature-mismatch",
              "title": "Signature verification failed",
              "status": 401,
              "detail": "The X-Aventra-Signature header does not match the expected HMAC. Verify your signing logic.",
              "instance": "/v1/parties"
            }
          }
        }
      },
      "InsufficientScope": {
        "description": "403 — the key is valid but lacks the scope this endpoint requires. Scopes are enforced per endpoint; request only the scopes you need.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://aventraguard.com/errors/insufficient-scope",
              "title": "Insufficient scope",
              "status": 403,
              "detail": "Your API key does not have the required scope for this operation. Contact the AML operator to update your key's permissions.",
              "instance": "/v1/parties",
              "extensions": {
                "required_scope": "parties:write"
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "404 — resource doesn't exist, or exists outside your RE scope (indistinguishable by design).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://aventraguard.com/errors/not-found",
              "title": "Resource not found",
              "status": 404,
              "detail": "The requested resource does not exist or is not accessible with your API key.",
              "instance": "/v1/parties/10421"
            }
          }
        }
      },
      "IdempotencyConflict": {
        "description": "409 — the same Idempotency-Key was reused with a different request body.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://aventraguard.com/errors/idempotency-conflict",
              "title": "Idempotency conflict",
              "status": 409,
              "detail": "A request with the same Idempotency-Key has already been made with a different request body.",
              "instance": "/v1/transactions"
            }
          }
        }
      },
      "BatchCompletedConflict": {
        "description": "409 — the batch has already completed and cannot be cancelled.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://aventraguard.com/errors/invalid-request",
              "title": "Invalid request",
              "status": 409,
              "detail": "Batch already completed; cannot cancel.",
              "instance": "/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc/cancel"
            }
          }
        }
      },
      "ValidationError": {
        "description": "422 — one or more request fields failed validation. Iterate extensions.fields[].",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://aventraguard.com/errors/validation-error",
              "title": "Validation failed",
              "status": 422,
              "detail": "One or more fields did not pass validation.",
              "instance": "/v1/transactions",
              "extensions": {
                "fields": [
                  {
                    "field": "currency",
                    "message": "currency must be a valid ISO 4217 code (e.g., CAD, USD)."
                  }
                ]
              }
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "413 — the NDJSON body exceeds the 4 MiB cap. Split into smaller batches.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://aventraguard.com/errors/invalid-request",
              "title": "Payload too large",
              "status": 413,
              "detail": "Request body exceeds the 4 MiB limit. Split into smaller batches.",
              "instance": "/v1/transactions/batch"
            }
          }
        }
      },
      "RateLimited": {
        "description": "429 — per-key sliding-window rate exceeded (600 reads / 60 writes per minute). Honour the Retry-After header.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying. Always present on a 429.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://aventraguard.com/errors/rate-limit-exceeded",
              "title": "Rate limit exceeded",
              "status": 429,
              "detail": "Your API key has exceeded its request limit. Retry after the time indicated.",
              "instance": "/v1/transactions",
              "extensions": {
                "retry_after_seconds": 60
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "500 — unexpected server failure. Retry with capped exponential backoff.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://aventraguard.com/errors/internal-error",
              "title": "Internal server error",
              "status": 500,
              "detail": "An unexpected error occurred. Our team has been notified. Please try again later.",
              "instance": "/v1/parties"
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "503 — transient downstream failure (DB write, screening queue). Retry with backoff, keeping any Idempotency-Key stable.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://aventraguard.com/errors/service-unavailable",
              "title": "Service temporarily unavailable",
              "status": 503,
              "detail": "The service is temporarily unavailable. Please retry in a moment.",
              "instance": "/v1/transactions"
            }
          }
        }
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 7807 problem detail (Content-Type: application/problem+json). Switch on `type`, not `title`/`detail`.",
        "required": [
          "type",
          "title",
          "status"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "Stable URI under https://aventraguard.com/errors/. The last path segment is the error code, e.g. `validation-error`.",
            "example": "https://aventraguard.com/errors/validation-error"
          },
          "title": {
            "type": "string",
            "description": "Short human label.",
            "example": "Validation failed"
          },
          "status": {
            "type": "integer",
            "description": "HTTP status, same as the response status line.",
            "example": 422
          },
          "detail": {
            "type": "string",
            "description": "Friendly explanation aimed at the integrator. Never leaks internal state.",
            "example": "One or more fields did not pass validation."
          },
          "instance": {
            "type": "string",
            "description": "The request URL path; useful for log correlation.",
            "example": "/v1/transactions"
          },
          "extensions": {
            "type": "object",
            "description": "Error-specific payload.",
            "additionalProperties": true,
            "properties": {
              "required_scope": {
                "type": "string",
                "description": "On insufficient-scope (403): the scope the operator needs to add."
              },
              "retry_after_seconds": {
                "type": "integer",
                "description": "On rate-limit-exceeded (429): seconds to wait."
              },
              "fields": {
                "type": "array",
                "description": "On validation-error (422): the failing fields.",
                "items": {
                  "$ref": "#/components/schemas/FieldError"
                }
              }
            }
          }
        }
      },
      "FieldError": {
        "type": "object",
        "required": [
          "field",
          "message"
        ],
        "properties": {
          "field": {
            "type": "string",
            "example": "party_type"
          },
          "message": {
            "type": "string",
            "example": "party_type must be 'individual' or 'business'."
          }
        }
      },
      "Status": {
        "type": "object",
        "description": "Shallow health response from GET /v1/status (no auth).",
        "required": [
          "status",
          "api_version",
          "build_sha",
          "current_time"
        ],
        "properties": {
          "status": {
            "type": "string",
            "example": "ok"
          },
          "api_version": {
            "type": "string",
            "example": "v1"
          },
          "build_sha": {
            "type": "string",
            "description": "Build commit SHA, or \"unknown\" if AML_BUILD_SHA was not set at deploy.",
            "example": "abc1234deadbeef"
          },
          "current_time": {
            "type": "string",
            "format": "date-time",
            "example": "2026-06-05T18:00:00Z"
          }
        }
      },
      "GovernmentIdType": {
        "type": "string",
        "description": "Closed enum of government identification document types (individuals only).",
        "enum": [
          "drivers_licence",
          "passport",
          "birth_certificate",
          "provincial_health_card",
          "citizenship_card",
          "permanent_resident_card",
          "record_of_landing",
          "nexus",
          "secure_certificate_of_indian_status",
          "other"
        ]
      },
      "CreatePartyRequest": {
        "type": "object",
        "description": "Body for POST /v1/parties (and each line of POST /v1/parties/batch). Upserts by (re_id, source_system='public_api', source_party_id). PCMLTFA individual-identification fields are individual-only; sensitive PII (birth_date, government_id_number) is stored encrypted at rest and is NEVER returned in any response.",
        "required": [
          "source_party_id",
          "party_type"
        ],
        "properties": {
          "source_party_id": {
            "type": "string",
            "description": "Your stable customer ID; forms the upsert key together with your RE."
          },
          "party_type": {
            "type": "string",
            "enum": [
              "individual",
              "business"
            ],
            "description": "individual or business."
          },
          "given_name": {
            "type": "string",
            "description": "Individual given (first) name."
          },
          "family_name": {
            "type": "string",
            "description": "Individual family (last) name."
          },
          "birth_date": {
            "type": "string",
            "format": "date",
            "writeOnly": true,
            "description": "ISO 8601 (YYYY-MM-DD). Individuals only. WRITE-ONLY: stored encrypted, never returned. A future date or age > 150y returns 422; re-ingest preserves an existing value."
          },
          "legal_name": {
            "type": "string",
            "description": "Full legal name for individuals."
          },
          "business_name": {
            "type": "string",
            "description": "Registered business name."
          },
          "email": {
            "type": "string",
            "description": "Contact email."
          },
          "phone": {
            "type": "string",
            "description": "Contact phone in E.164."
          },
          "address_country": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 (e.g. CA)."
          },
          "address_region": {
            "type": "string",
            "description": "Province / state."
          },
          "address_city": {
            "type": "string",
            "description": "City."
          },
          "address_postal": {
            "type": "string",
            "description": "Postal / ZIP."
          },
          "kyc_status": {
            "type": "string",
            "description": "Free-form KYC status code from your KYC vendor."
          },
          "source_created_at": {
            "type": "string",
            "format": "date-time",
            "description": "RFC 3339 timestamp of when you created the record."
          },
          "country_of_residence": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2. Individuals only. Stored uppercased."
          },
          "country_of_citizenship": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2. Individuals only. Stored uppercased."
          },
          "occupation": {
            "type": "string",
            "maxLength": 200,
            "description": "Individuals only. Stored plaintext (lower sensitivity)."
          },
          "employer_name": {
            "type": "string",
            "maxLength": 100,
            "description": "Individuals only. Stored plaintext."
          },
          "government_id_type": {
            "allOf": [
              {
                "$ref": "#/components/schemas/GovernmentIdType"
              }
            ],
            "description": "Individuals only."
          },
          "government_id_type_other": {
            "type": "string",
            "maxLength": 200,
            "description": "Required iff government_id_type == 'other'; setting it when type isn't 'other' (or omitting it when it is) returns 422. Cleared automatically when type is corrected to a non-'other' value."
          },
          "government_id_issuing_country": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2. Individuals only. Stored uppercased."
          },
          "government_id_issuing_province": {
            "type": "string",
            "maxLength": 20,
            "description": "Province/state code. Individuals only."
          },
          "government_id_issuing_province_name": {
            "type": "string",
            "maxLength": 100,
            "description": "Province/state name. Individuals only."
          },
          "government_id_number": {
            "type": "string",
            "maxLength": 100,
            "writeOnly": true,
            "description": "SENSITIVE PII. Individuals only. Stored encrypted at rest (AES-256-GCM); NEVER returned in any response. When the server's encryption key is not configured (non-production) it is accepted (201) but not persisted."
          }
        }
      },
      "Party": {
        "type": "object",
        "description": "A party as returned by POST /v1/parties (201) and GET /v1/parties/{id} (200). Sensitive PII (DOB, government_id_number, names where flagged) is never echoed.",
        "required": [
          "id",
          "source_party_id",
          "party_type",
          "risk_tier",
          "kyc_status",
          "edd_required",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Numeric AventraGuard party ID (as a string).",
            "example": "10421"
          },
          "source_party_id": {
            "type": "string",
            "example": "acme-cust-00001"
          },
          "party_type": {
            "type": "string",
            "enum": [
              "individual",
              "business"
            ]
          },
          "risk_tier": {
            "type": "string",
            "description": "Risk tier (e.g. T1, T2, T3).",
            "example": "T2"
          },
          "kyc_status": {
            "type": "string",
            "description": "May be empty.",
            "example": "verified"
          },
          "address_country": {
            "type": "string",
            "example": "CA"
          },
          "edd_required": {
            "type": "boolean"
          },
          "next_review_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Next scheduled review; absent when none scheduled."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PartyListItem": {
        "type": "object",
        "description": "One element of the items array in GET /v1/parties.",
        "required": [
          "id",
          "source_party_id",
          "party_type",
          "risk_tier",
          "edd_required"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "10421"
          },
          "source_party_id": {
            "type": "string",
            "example": "acme-cust-00001"
          },
          "party_type": {
            "type": "string",
            "enum": [
              "individual",
              "business"
            ]
          },
          "risk_tier": {
            "type": "string",
            "example": "T2"
          },
          "edd_required": {
            "type": "boolean"
          },
          "next_review_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "tier_set_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "PartyListResponse": {
        "type": "object",
        "required": [
          "items",
          "total",
          "limit"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PartyListItem"
            }
          },
          "total": {
            "type": "integer",
            "example": 487
          },
          "limit": {
            "type": "integer",
            "example": 50
          },
          "next_cursor": {
            "type": "string",
            "description": "Empty when there are no more pages.",
            "example": "2"
          }
        }
      },
      "RecomputeResult": {
        "type": "object",
        "description": "Result of POST /v1/parties/{id}/recompute.",
        "required": [
          "party_id",
          "previous_tier",
          "current_tier",
          "action",
          "change_id"
        ],
        "properties": {
          "party_id": {
            "type": "integer",
            "example": 10421
          },
          "previous_tier": {
            "type": "string",
            "example": "T2"
          },
          "current_tier": {
            "type": "string",
            "example": "T3"
          },
          "action": {
            "type": "string",
            "enum": [
              "unchanged",
              "upgraded",
              "downgrade_pending",
              "downgraded"
            ],
            "description": "Downgrades require MLRO approval, so they queue as downgrade_pending."
          },
          "change_id": {
            "type": "integer",
            "example": 88421
          }
        }
      },
      "ScreeningHit": {
        "type": "object",
        "description": "One full-detail screening hit from GET /v1/parties/{id}/screening-hits. The entry_name/matched_name/screened_name/subject_name/disposed_by fields are PCMLTFA s.66 tipping-off sensitive.",
        "required": [
          "id",
          "list_source",
          "hit_kind",
          "match_type",
          "match_score",
          "disposition",
          "first_seen_at",
          "last_seen_at",
          "subject_type"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Screening-hit row ID. Equals the bulk list's hit_id and the webhook hit_id.",
            "example": 902
          },
          "re_id": {
            "type": "string",
            "format": "uuid",
            "description": "Reporting-entity UUID."
          },
          "party_id": {
            "type": "integer",
            "nullable": true,
            "description": "Party the hit is on; omitted/null for beneficial-owner / intermediary chain-node hits.",
            "example": 10421
          },
          "list_source": {
            "type": "string",
            "description": "Source list (e.g. OSFI). NOTE: the field is list_source, not list_code.",
            "example": "OSFI"
          },
          "entry_name": {
            "type": "string",
            "description": "Matched list-entry name. TIPPING-OFF SENSITIVE — strip at your transform layer.",
            "example": "ALEKSANDR MORGAN"
          },
          "matched_name": {
            "type": "string",
            "description": "Matched name. TIPPING-OFF SENSITIVE.",
            "example": "ALEX MORGAN"
          },
          "screened_name": {
            "type": "string",
            "description": "Screened name. TIPPING-OFF SENSITIVE.",
            "example": "ALEX MORGAN"
          },
          "hit_kind": {
            "type": "string",
            "enum": [
              "sanctions",
              "pep",
              "adverse_media"
            ],
            "example": "sanctions"
          },
          "match_type": {
            "type": "string",
            "enum": [
              "exact",
              "fuzzy",
              "phonetic"
            ],
            "example": "fuzzy"
          },
          "match_score": {
            "type": "number",
            "format": "float",
            "example": 0.94
          },
          "disposition": {
            "type": "string",
            "enum": [
              "pending",
              "confirmed",
              "cleared"
            ],
            "description": "NOTE: the field is disposition with values pending/confirmed/cleared, not status/open.",
            "example": "confirmed"
          },
          "disposed_by": {
            "type": "string",
            "nullable": true,
            "description": "Who dispositioned the hit. TIPPING-OFF SENSITIVE.",
            "example": "user:..."
          },
          "disposed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "first_seen_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_seen_at": {
            "type": "string",
            "format": "date-time"
          },
          "subject_type": {
            "type": "string",
            "enum": [
              "party",
              "beneficial_owner",
              "intermediary_entity"
            ],
            "example": "party"
          },
          "subject_id": {
            "type": "integer",
            "nullable": true,
            "example": 10421
          },
          "subject_name": {
            "type": "string",
            "description": "TIPPING-OFF SENSITIVE.",
            "example": "ALEX MORGAN"
          },
          "beneficial_owner_id": {
            "type": "integer",
            "nullable": true
          },
          "chain_node_id": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "ScreeningHitListResponse": {
        "type": "object",
        "description": "Response from GET /v1/parties/{id}/screening-hits. Note: this per-party endpoint returns only items+total (no limit/next_cursor).",
        "required": [
          "items",
          "total"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ScreeningHit"
            }
          },
          "total": {
            "type": "integer",
            "example": 1
          }
        }
      },
      "BulkScreeningHit": {
        "type": "object",
        "description": "One element of the global GET /v1/screening-hits list — the non-tipping-off subset (no matched/screened/list-entry names).",
        "required": [
          "hit_id",
          "subject_type",
          "list_source",
          "hit_kind",
          "match_type",
          "match_score",
          "disposition",
          "first_seen_at",
          "last_seen_at"
        ],
        "properties": {
          "hit_id": {
            "type": "integer",
            "description": "Screening-hit row ID; correlates with the party.screening_hit webhook hit_id and the per-party endpoint's id.",
            "example": 50231
          },
          "party_id": {
            "type": "integer",
            "nullable": true,
            "description": "Present for party and beneficial_owner subject hits (for a beneficial owner it is the parent party); omitted only when subject_type is intermediary_entity.",
            "example": 88901
          },
          "subject_type": {
            "type": "string",
            "enum": [
              "party",
              "beneficial_owner",
              "intermediary_entity"
            ],
            "example": "party"
          },
          "list_source": {
            "type": "string",
            "example": "osfi"
          },
          "hit_kind": {
            "type": "string",
            "enum": [
              "sanctions",
              "pep",
              "adverse_media"
            ],
            "example": "sanctions"
          },
          "match_type": {
            "type": "string",
            "enum": [
              "exact",
              "fuzzy",
              "phonetic"
            ],
            "example": "exact"
          },
          "match_score": {
            "type": "number",
            "format": "float",
            "example": 0.94
          },
          "disposition": {
            "type": "string",
            "enum": [
              "pending",
              "confirmed",
              "cleared"
            ],
            "example": "confirmed"
          },
          "first_seen_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_seen_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "BulkScreeningHitListResponse": {
        "type": "object",
        "required": [
          "items",
          "total",
          "limit"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BulkScreeningHit"
            }
          },
          "total": {
            "type": "integer",
            "example": 1
          },
          "limit": {
            "type": "integer",
            "example": 100
          },
          "next_cursor": {
            "type": "string",
            "description": "Empty when there are no more pages.",
            "example": ""
          }
        }
      },
      "IngestTransactionRequest": {
        "type": "object",
        "description": "Body for POST /v1/transactions (and each line of POST /v1/transactions/batch).",
        "required": [
          "source_transaction_id",
          "source_party_id",
          "amount",
          "currency",
          "status",
          "method",
          "action",
          "occurred_at",
          "source_created_at"
        ],
        "properties": {
          "source_transaction_id": {
            "type": "string",
            "description": "Your stable transaction ID."
          },
          "source_reference_id": {
            "type": "string",
            "description": "Your internal reference (order ID, etc.)."
          },
          "source_party_id": {
            "type": "string",
            "description": "The party who initiated; null linkage is allowed (links once the party is pushed)."
          },
          "amount": {
            "type": "string",
            "description": "Decimal as string (\"123.45\") to preserve precision.",
            "example": "9850.00"
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217 (CAD, USD, EUR).",
            "example": "CAD"
          },
          "fee": {
            "type": "string",
            "description": "Same format as amount."
          },
          "status": {
            "type": "string",
            "description": "Your status code (e.g. completed, pending).",
            "example": "completed"
          },
          "method": {
            "type": "string",
            "description": "Payment method (e.g. e_transfer, wire, card).",
            "example": "e_transfer"
          },
          "action": {
            "type": "string",
            "description": "Logical action; validated against the canonical enum. Unknown verbs return 422.",
            "enum": [
              "sale",
              "deposit",
              "withdrawal",
              "transfer",
              "payout",
              "refund",
              "reserve",
              "complete",
              "cancel",
              "void",
              "verification",
              "request",
              "bill_payment",
              "other"
            ],
            "example": "transfer"
          },
          "direction": {
            "type": "string",
            "enum": [
              "credit",
              "debit",
              "neutral"
            ],
            "description": "Optional. Overrides the action-derived direction heuristic when present; a bad value returns 422."
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time",
            "description": "RFC 3339 — when the transaction happened."
          },
          "source_created_at": {
            "type": "string",
            "format": "date-time",
            "description": "RFC 3339 — when your system created the record."
          }
        }
      },
      "IngestTransactionResponse": {
        "type": "object",
        "description": "202 Accepted body from POST /v1/transactions.",
        "required": [
          "id",
          "source_transaction_id",
          "status",
          "screening_queued"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Server-assigned transaction id (UUID).",
            "example": "0193b6a8-1e1a-7c00-9d77-3f0a2b1c4d5e"
          },
          "source_transaction_id": {
            "type": "string",
            "example": "acme-tx-2026-05-30-0001"
          },
          "status": {
            "type": "string",
            "description": "Always \"accepted\".",
            "example": "accepted"
          },
          "screening_queued": {
            "type": "boolean",
            "description": "Always true on a 202.",
            "example": true
          }
        }
      },
      "Transaction": {
        "type": "object",
        "description": "Shape returned by GET /v1/transactions/{id} and list items. raw_payload and card/bank details are never echoed.",
        "required": [
          "id",
          "source_transaction_id",
          "source_party_id",
          "party_linked",
          "amount",
          "currency",
          "status",
          "method",
          "direction",
          "action",
          "occurred_at",
          "source_created_at",
          "ingested_at",
          "last_seen_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Numeric transaction id (as a string).",
            "example": "12345"
          },
          "source_transaction_id": {
            "type": "string"
          },
          "source_reference_id": {
            "type": "string"
          },
          "source_party_id": {
            "type": "string"
          },
          "party_id": {
            "type": "string",
            "nullable": true,
            "description": "Resolved party id; null when the party hasn't been ingested yet (orphan).",
            "example": "678"
          },
          "party_linked": {
            "type": "boolean",
            "description": "false (with party_id null) means the transaction arrived before its party; it links once the party is pushed (or via the relink sweep)."
          },
          "amount": {
            "type": "string",
            "example": "9850.00000000"
          },
          "currency": {
            "type": "string",
            "example": "CAD"
          },
          "status": {
            "type": "string",
            "example": "completed"
          },
          "method": {
            "type": "string",
            "example": "e_transfer"
          },
          "direction": {
            "type": "string",
            "enum": [
              "credit",
              "debit",
              "neutral"
            ],
            "example": "debit"
          },
          "action": {
            "type": "string",
            "example": "transfer"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "source_created_at": {
            "type": "string",
            "format": "date-time"
          },
          "ingested_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_seen_at": {
            "type": "string",
            "format": "date-time",
            "description": "AML's last-ingest time, not the source's update time."
          }
        }
      },
      "TransactionListResponse": {
        "type": "object",
        "required": [
          "items",
          "total",
          "limit"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Transaction"
            }
          },
          "total": {
            "type": "integer",
            "example": 1
          },
          "limit": {
            "type": "integer",
            "example": 50
          },
          "next_cursor": {
            "type": "string",
            "example": ""
          }
        }
      },
      "BatchAccepted": {
        "type": "object",
        "description": "202 response from POST /v1/transactions/batch and POST /v1/parties/batch.",
        "required": [
          "batch_id",
          "status_url",
          "state",
          "total_rows"
        ],
        "properties": {
          "batch_id": {
            "type": "string",
            "format": "uuid",
            "example": "0193b6d4-9999-7a00-bf01-aaaabbbbcccc"
          },
          "status_url": {
            "type": "string",
            "example": "/v1/batches/0193b6d4-9999-7a00-bf01-aaaabbbbcccc"
          },
          "state": {
            "type": "string",
            "example": "accepted"
          },
          "total_rows": {
            "type": "integer",
            "example": 4821
          }
        }
      },
      "Batch": {
        "type": "object",
        "description": "Batch status from GET /v1/batches/{id} (and the body of POST /v1/batches/{id}/cancel). state: accepted -> processing -> completed (or cancelled).",
        "required": [
          "batch_id",
          "resource",
          "state",
          "total_rows",
          "accepted_rows",
          "processed_rows",
          "failed_rows",
          "created_at"
        ],
        "properties": {
          "batch_id": {
            "type": "string",
            "format": "uuid",
            "example": "0193b6d4-9999-7a00-bf01-aaaabbbbcccc"
          },
          "resource": {
            "type": "string",
            "enum": [
              "transactions",
              "parties"
            ],
            "example": "transactions"
          },
          "state": {
            "type": "string",
            "enum": [
              "accepted",
              "processing",
              "completed",
              "cancelled"
            ],
            "example": "completed"
          },
          "total_rows": {
            "type": "integer",
            "example": 4821
          },
          "accepted_rows": {
            "type": "integer",
            "example": 4820
          },
          "processed_rows": {
            "type": "integer",
            "example": 4818
          },
          "failed_rows": {
            "type": "integer",
            "example": 3
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "started_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Absent until the worker starts."
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Absent until terminal."
          },
          "cancel_reason": {
            "type": "string",
            "nullable": true,
            "description": "Present only on cancelled batches with a reason."
          }
        }
      },
      "BatchError": {
        "type": "object",
        "description": "One failed row from GET /v1/batches/{id}/errors. message is PII-free (field + reason, or a DB SQLSTATE), never the row's value.",
        "required": [
          "line",
          "message"
        ],
        "properties": {
          "line": {
            "type": "integer",
            "example": 17
          },
          "message": {
            "type": "string",
            "example": "currency: must be a valid ISO 4217 code (e.g., CAD, USD)."
          }
        }
      },
      "BatchErrorsPage": {
        "type": "object",
        "required": [
          "batch_id",
          "items",
          "total",
          "limit"
        ],
        "properties": {
          "batch_id": {
            "type": "string",
            "format": "uuid"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BatchError"
            }
          },
          "total": {
            "type": "integer",
            "example": 3
          },
          "limit": {
            "type": "integer",
            "example": 50
          },
          "next_cursor": {
            "type": "string",
            "example": ""
          }
        }
      },
      "CancelBatchRequest": {
        "type": "object",
        "description": "Optional body for POST /v1/batches/{id}/cancel.",
        "properties": {
          "reason": {
            "type": "string",
            "example": "wrong source file"
          }
        }
      },
      "Alert": {
        "type": "object",
        "description": "An alert (list item and detail share the same shape). Note: the alert detail omits re_id.",
        "required": [
          "id",
          "alert_type",
          "severity",
          "status",
          "rule_code",
          "detail",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "33010"
          },
          "alert_type": {
            "type": "string",
            "example": "rule"
          },
          "severity": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high",
              "critical"
            ],
            "example": "high"
          },
          "status": {
            "type": "string",
            "description": "e.g. open, escalated (after true_positive), dismissed (after false_positive).",
            "example": "open"
          },
          "rule_code": {
            "type": "string",
            "example": "STRUCTURED_DEPOSITS"
          },
          "detail": {
            "type": "string",
            "example": "Three deposits totalling 9,850 CAD within 24 hours, each below the 10,000 CAD LCTR threshold."
          },
          "party_id": {
            "type": "string",
            "nullable": true,
            "example": "10421"
          },
          "amount": {
            "type": "string",
            "example": "9850.00"
          },
          "currency": {
            "type": "string",
            "example": "CAD"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AlertListResponse": {
        "type": "object",
        "required": [
          "items",
          "total",
          "limit"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Alert"
            }
          },
          "total": {
            "type": "integer",
            "example": 1
          },
          "limit": {
            "type": "integer",
            "example": 50
          },
          "next_cursor": {
            "type": "string",
            "example": ""
          }
        }
      },
      "DispositionRequest": {
        "type": "object",
        "description": "Body for PATCH /v1/alerts/{id}/disposition.",
        "required": [
          "disposition"
        ],
        "properties": {
          "disposition": {
            "type": "string",
            "enum": [
              "true_positive",
              "false_positive"
            ],
            "description": "true_positive escalates the alert; false_positive dismisses it."
          },
          "notes": {
            "type": "string",
            "description": "Reviewer notes. The platform supplies a default note when absent."
          }
        }
      },
      "CaseListItem": {
        "type": "object",
        "description": "One element of GET /v1/cases.",
        "required": [
          "id",
          "re_id",
          "party_id",
          "status",
          "alert_count",
          "opened_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "1502"
          },
          "re_id": {
            "type": "string",
            "format": "uuid",
            "example": "0193b6a8-1e1a-7c00-9d77-3f0a2b1c4d5e"
          },
          "party_id": {
            "type": "string",
            "example": "10421"
          },
          "status": {
            "type": "string",
            "example": "investigating"
          },
          "alert_count": {
            "type": "integer",
            "example": 3
          },
          "opened_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "due_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Omitted for no-deadline cases."
          },
          "overdue": {
            "type": "boolean"
          }
        }
      },
      "CaseListResponse": {
        "type": "object",
        "required": [
          "items",
          "total",
          "limit"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CaseListItem"
            }
          },
          "total": {
            "type": "integer",
            "example": 1
          },
          "limit": {
            "type": "integer",
            "example": 50
          },
          "next_cursor": {
            "type": "string",
            "example": ""
          }
        }
      },
      "CaseDetail": {
        "type": "object",
        "description": "Case detail from GET /v1/cases/{id}. ids are strings here (webhooks use integer case_id/party_id). re_id IS present (unlike the alert detail). closed_at/due_at are omitted for open / no-deadline cases. There is no updated_at on the detail.",
        "required": [
          "id",
          "re_id",
          "party_id",
          "status",
          "case_type",
          "opened_at",
          "overdue",
          "alert_count"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "1502"
          },
          "re_id": {
            "type": "string",
            "format": "uuid",
            "example": "0193b6a8-1e1a-7c00-9d77-3f0a2b1c4d5e"
          },
          "party_id": {
            "type": "string",
            "example": "10421"
          },
          "status": {
            "type": "string",
            "example": "investigating"
          },
          "case_type": {
            "type": "string",
            "example": "aml"
          },
          "opened_at": {
            "type": "string",
            "format": "date-time"
          },
          "due_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Omitted for no-deadline cases."
          },
          "closed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Included only when the case is closed."
          },
          "overdue": {
            "type": "boolean"
          },
          "alert_count": {
            "type": "integer",
            "example": 3
          }
        }
      },
      "FilingListItem": {
        "type": "object",
        "description": "One element of GET /v1/filings.",
        "required": [
          "id",
          "case_id",
          "status",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "2204"
          },
          "case_id": {
            "type": "string",
            "example": "1502"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "pending_approval",
              "approved",
              "submitted",
              "returned"
            ],
            "example": "submitted"
          },
          "drafted_by": {
            "type": "string",
            "example": "analyst@acme.example.com"
          },
          "approved_by": {
            "type": "string",
            "nullable": true,
            "example": "mlro@acme.example.com"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "submitted_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "fintrac_receipt_id": {
            "type": "string",
            "nullable": true,
            "example": "FINTRAC-2026-00417"
          }
        }
      },
      "FilingListResponse": {
        "type": "object",
        "required": [
          "items",
          "total",
          "limit"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FilingListItem"
            }
          },
          "total": {
            "type": "integer",
            "example": 1
          },
          "limit": {
            "type": "integer",
            "example": 50
          },
          "next_cursor": {
            "type": "string",
            "example": ""
          }
        }
      },
      "FilingDetail": {
        "type": "object",
        "description": "Filing detail from GET /v1/filings/{id}. The narrative and F2R XML are deliberately omitted.",
        "required": [
          "id",
          "re_id",
          "case_id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "2204"
          },
          "re_id": {
            "type": "string",
            "format": "uuid",
            "example": "0193b6a8-1e1a-7c00-9d77-3f0a2b1c4d5e"
          },
          "case_id": {
            "type": "string",
            "example": "1502"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "pending_approval",
              "approved",
              "submitted",
              "returned"
            ],
            "example": "submitted"
          },
          "drafted_by": {
            "type": "string",
            "nullable": true,
            "example": "analyst@acme.example.com"
          },
          "drafted_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "approved_by": {
            "type": "string",
            "nullable": true,
            "example": "mlro@acme.example.com"
          },
          "approved_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "exported_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "submitted_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "fintrac_receipt_id": {
            "type": "string",
            "nullable": true,
            "example": "FINTRAC-2026-00417"
          }
        }
      },
      "Webhook": {
        "type": "object",
        "description": "A webhook registration as returned by GET /v1/webhooks (the secret is never returned here).",
        "required": [
          "id",
          "url",
          "event_filters",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "example": "0193b6c0-d1e2-7a00-bf01-1234567890ab"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "example": "https://hooks.acme.example.com/aventraguard"
          },
          "label": {
            "type": "string",
            "example": "acme-prod"
          },
          "event_filters": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Empty array = deliver everything; a non-empty array restricts to those event types."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_by": {
            "type": "string",
            "example": "ui:admin@acme.example.com"
          }
        }
      },
      "WebhookListResponse": {
        "type": "object",
        "required": [
          "items",
          "total"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Webhook"
            }
          },
          "total": {
            "type": "integer",
            "example": 1
          }
        }
      },
      "CreateWebhookRequest": {
        "type": "object",
        "description": "Body for POST /v1/webhooks.",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "HTTPS URL of your receiver. http:// and private/loopback hosts are rejected.",
            "example": "https://hooks.acme.example.com/aventraguard"
          },
          "label": {
            "type": "string",
            "description": "Free-form label for the admin UI.",
            "example": "acme-prod"
          },
          "event_filters": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "alert.created",
                "alert.disposed",
                "case.opened",
                "case.closed",
                "filing.submitted",
                "party.screening_hit",
                "party.screening_hit.created",
                "party.risk_tier_changed",
                "batch.completed"
              ]
            },
            "description": "List of event types. Empty / omitted = all events."
          }
        }
      },
      "WebhookWithSecret": {
        "type": "object",
        "description": "201 response from POST /v1/webhooks. The secret appears ONLY on this response.",
        "required": [
          "id",
          "url",
          "event_filters",
          "created_at",
          "secret"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "example": "0193b6c0-d1e2-7a00-bf01-1234567890ab"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "example": "https://hooks.acme.example.com/aventraguard"
          },
          "label": {
            "type": "string",
            "example": "acme-prod"
          },
          "event_filters": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_by": {
            "type": "string",
            "example": "public_api_key:0193b6a8-aaaa-bbbb-cccc-1234567890ab"
          },
          "secret": {
            "type": "string",
            "description": "The signing secret (whsec_...), returned exactly once. Store it immediately.",
            "example": "whsec_3f8a9e2b1c0d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f"
          }
        }
      },
      "AuditLogEntry": {
        "type": "object",
        "description": "One audit-log entry. Allow-listed view: actor identity is masked to actor_class; before_hash/after_hash/row_hash/prev_hash/re_id are never returned.",
        "required": [
          "id",
          "action_type",
          "subject_type",
          "subject_id",
          "actor_class",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Monotonic sequence; safe for pagination cursors.",
            "example": 14821
          },
          "action_type": {
            "type": "string",
            "description": "Allow-listed audit action type.",
            "example": "BATCH_INGEST_COMPLETED"
          },
          "subject_type": {
            "type": "string",
            "description": "Entity class (e.g. rule, integration, transaction, public_api_batch).",
            "example": "public_api_batch"
          },
          "subject_id": {
            "type": "string",
            "description": "Entity primary key as a string.",
            "example": "01JWTX4R8QK3V2F9YZPH5DCMBA"
          },
          "actor_class": {
            "type": "string",
            "enum": [
              "platform_user",
              "system",
              "operator_cli",
              "api_key",
              "ai_engine"
            ],
            "description": "Masked actor category; raw user/key UUIDs are never returned.",
            "example": "system"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "summary": {
            "type": "string",
            "description": "Transformed structured context for an explicit Bucket-A subset only; omitted otherwise (never the raw after_hash).",
            "example": "accepted=498 failed=2 processed=500"
          }
        }
      },
      "AuditLogResponse": {
        "type": "object",
        "description": "Response envelope for GET /v1/audit-log. NOTE: this endpoint uses a data/next_cursor/has_more envelope (not the items/total envelope used elsewhere).",
        "required": [
          "data",
          "has_more"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AuditLogEntry"
            }
          },
          "next_cursor": {
            "type": "string",
            "description": "Opaque cursor (last id seen); empty/absent when there are no more pages.",
            "example": "14816"
          },
          "has_more": {
            "type": "boolean",
            "example": true
          }
        }
      }
    }
  }
}
