{"openapi":"3.1.0","info":{"title":"CAT White-Label Compliance API","version":"1.0.0","description":"Multi-tenant PCI DSS v4.1 compliance assessment platform for ISOs, banks, and MSPs.\n\n**Three integration shapes**, all backed by the same engine:\n- **REST API** (this spec) — server-side from your CRM\n- **MCP server** at `POST /mcp` — for AI agents (Claude, ChatGPT)\n- **Iframe embed** at `GET /portal/embed` — drop into your customer portal\n\n**Async pattern.** AI calls (determine, combined-question, analyze) take 8–60s.\nBy default they return `202 { jobId, pollUrl }` immediately; the client polls\n`GET /v1/jobs/.../{jobId}` every 2–3s until `status === \"COMPLETED\"`.\nPass `?wait=1` for a synchronous wrapper bounded at 27s (works for fast cases).\n\n**Tenant isolation.** Every record carries a `partnerId` enforced server-side.\nCross-partner reads return 404, never the other partner's data.","contact":{"name":"Partner success","email":"partnerSuccess@catcompliance.example"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://api.complianceassessmenttool.com","description":"Production"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"X-API-Key"}},"schemas":{"Merchant":{"type":"object","required":["id","partnerId","name","complianceStatus","createdAt"],"properties":{"id":{"type":"string","example":"mrc_xxxxx"},"partnerId":{"type":"string","example":"ptr_test_demo"},"externalMerchantId":{"type":"string","description":"Your internal ID"},"name":{"type":"string"},"email":{"type":"string","format":"email"},"website":{"type":"string","format":"uri"},"complianceStatus":{"type":"string","enum":["not_started","payment_methods_added","saq_determined","questions_in_progress","review_in_progress","compliant","expired"]},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}}},"PaymentMethod":{"type":"object","required":["id","merchantId","partnerId","name","description"],"properties":{"id":{"type":"string","example":"pm_xxxxx"},"merchantId":{"type":"string"},"partnerId":{"type":"string"},"name":{"type":"string"},"description":{"type":"string","minLength":10,"maxLength":3000},"channel":{"type":"string","enum":["website","countertop","mobile","mobileNoReader","kiosk","links","phoneVT","mailOrder","recurring","selfStore","other"]},"vendor":{"type":"string"},"saqType":{"type":"string"}}},"SAQAttempt":{"type":"object","properties":{"id":{"type":"string","example":"att_xxxxx"},"paymentMethodId":{"type":"string"},"merchantId":{"type":"string"},"partnerId":{"type":"string"},"saqType":{"type":"string","example":"SAQ B-IP"},"status":{"type":"string","enum":["Step_2_Started","Step_2_Completed","Step_3_Review","sealed","sealed_ready"]},"threadId":{"type":"string","description":"AI conversation thread ID for cross-call continuity"}}},"Question":{"type":"object","properties":{"controlId":{"type":"string","example":"9.5.1.1"},"title":{"type":"string"},"requirement":{"type":"string","description":"Full PCI DSS v4.1 requirement text"},"guidance":{"type":"string"},"applicabilityNote":{"type":"string"},"topLevel":{"type":"string","example":"9"},"sectionTitle":{"type":"string","example":"Restrict Physical Access to Cardholder Data"},"answer":{"type":"string","nullable":true},"status":{"type":"string","enum":["empty","In Place","Not In Place","Not Applicable","In Place with Compensating Control"]},"combinedQuestion":{"type":"string","nullable":true,"description":"AI-generated merchant-friendly version of the requirement"},"suggestedAnswers":{"type":"array","nullable":true,"items":{"type":"object","properties":{"answer":{"type":"string"},"type":{"type":"string","enum":["In Place","Not In Place","Not Applicable","In Place with Compensating Control"]}}}}}},"Section":{"type":"object","properties":{"topLevel":{"type":"string","example":"9"},"title":{"type":"string"},"total":{"type":"integer"},"answered":{"type":"integer"}}},"JobAck":{"type":"object","required":["jobId","pollUrl","status"],"properties":{"jobId":{"type":"string","format":"uuid"},"pollUrl":{"type":"string","description":"Path to GET for the job status"},"status":{"type":"string","enum":["PROCESSING"]},"kind":{"type":"string","enum":["combined","analyze"]},"attemptId":{"type":"string"},"controlId":{"type":"string"}}},"JobStatus":{"type":"object","properties":{"status":{"type":"string","enum":["PROCESSING","COMPLETED","FAILED"]},"result":{"type":"object","additionalProperties":true},"errorMessage":{"type":"string"}}},"DeterminationResult":{"type":"object","properties":{"saqType":{"type":"string","nullable":true,"example":"SAQ A"},"reason":{"type":"string","nullable":true},"qualifications":{"type":"array","items":{"type":"object","properties":{"question":{"type":"string"},"pciReference":{"type":"string"},"meetsQualification":{"type":"boolean","nullable":true},"explanation":{"type":"string"}}}},"followUpQuestion":{"type":"string","nullable":true},"followUpLinks":{"type":"array","items":{"type":"object","properties":{"url":{"type":"string"},"label":{"type":"string"}}}},"threadId":{"type":"string","nullable":true},"status":{"type":"string","enum":["determined","needs-followup","pending"]}}},"Analysis":{"type":"object","properties":{"complianceStatus":{"type":"string","enum":["In Place","Not In Place","Not Applicable","In Place with Compensating Control","empty"]},"reason":{"type":"string"},"suggestion":{"type":"string"}}},"ApiError":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","example":"VALIDATION_ERROR"},"message":{"type":"string"},"details":{"type":"object","additionalProperties":true}}}}},"paths":{"/health":{"get":{"summary":"Health check (public, no auth)","security":[],"responses":{"200":{"description":"Service is healthy"}}}},"/v1/merchants":{"get":{"summary":"List merchants under your partner account","parameters":[{"name":"limit","in":"query","schema":{"type":"integer","default":50,"maximum":100}}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Merchant"}},"nextToken":{"type":"string"}}}}}}}},"post":{"summary":"Create a merchant","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"externalMerchantId":{"type":"string"},"website":{"type":"string"}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Merchant"}}}}}}},"/v1/merchants/{merchantId}":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Get merchant","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Merchant"}}}}}},"put":{"summary":"Update merchant","responses":{"200":{"description":"OK"}}},"delete":{"summary":"Delete a merchant (sandbox tenants only)","description":"Removes the merchant along with its payment methods and attempts, freeing a slot against the sandbox 5-merchant cap so throwaway test records do not permanently consume your allowance. Returns 403 DELETE_NOT_ALLOWED on a live tenant, where a merchant anchors signed attestations and billing history.","responses":{"200":{"description":"Deleted, with counts of the cascaded children"},"403":{"description":"Live tenant — deletion is a support action"},"404":{"description":"Merchant not found"}}}},"/v1/merchants/{merchantId}/payment-methods":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Add payment method","description":"Each merchant can have multiple payment methods; each gets its own SAQ.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","description"],"properties":{"name":{"type":"string"},"description":{"type":"string","minLength":10},"channel":{"type":"string"},"vendor":{"type":"string"}}}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentMethod"}}}}}},"get":{"summary":"List merchant's payment methods","responses":{"200":{"description":"List","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/PaymentMethod"}}}}}}}}}},"/v1/merchants/{merchantId}/integrations/authorize-net":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Describe payment methods from the merchant's Authorize.Net account","description":"Reads the merchant's Authorize.Net account (market types, card-present vs card-not-present, Tap to Phone, recurring billing, from settings and up to 50 recent transactions) and returns one plain-English payment-method description per payment channel, ready for SAQ determination. With `createPaymentMethods: true` the payment methods are created for you. Credentials are used for this request only and are never stored. The gateway cannot show whether an online checkout is a hosted payment page or a form on the merchant's own site; determination asks that.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["apiLoginId","transactionKey"],"properties":{"apiLoginId":{"type":"string"},"transactionKey":{"type":"string"},"environment":{"type":"string","enum":["production","sandbox"],"default":"production"},"lookbackDays":{"type":"integer","minimum":1,"maximum":90,"default":30},"createPaymentMethods":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"{ profile, paymentMethods }"},"400":{"description":"Invalid or inactive Authorize.Net credentials (gatewayCode E00007 / E00008)"},"502":{"description":"Authorize.Net unavailable"}}}},"/v1/merchants/{merchantId}/portal-link":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Mint portal URLs for this merchant (iframe embed, standalone, platform redirect)","description":"Returns three URLs: `embedUrl` (drop into an iframe), `standalonePortalUrl` (open in a new tab), and `handoffUrl` (full-page platform redirect — we send the merchant back to your `returnUrl` with `?status=completed&attemptId=…&saqType=…` when they finish). URLs carry a merchant-scoped session token that expires after `ttlSeconds`, so they are safe to deliver to the merchant browser. The `returnUrl` host must be on your partner allowlist (configured during onboarding) or the call returns 400.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"returnUrl":{"type":"string","format":"uri","maxLength":2048,"description":"Where we redirect the merchant after finalize (platform-redirect mode). Host must be allowlisted."},"ttlSeconds":{"type":"integer","minimum":60,"maximum":86400,"default":3600}}}}}},"responses":{"200":{"description":"Portal URLs + session token","content":{"application/json":{"schema":{"type":"object","properties":{"merchantId":{"type":"string"},"merchantName":{"type":"string"},"embedUrl":{"type":"string","format":"uri"},"standalonePortalUrl":{"type":"string","format":"uri"},"handoffUrl":{"type":"string","format":"uri","nullable":true},"sessionToken":{"type":"string","nullable":true,"description":"cat_sess_… merchant-scoped token"},"expiresAt":{"type":"string","format":"date-time"},"ttlSeconds":{"type":"integer"},"authMode":{"type":"string","enum":["session-token","api-key"]}}}}}}}}},"/v1/merchants/{merchantId}/session-token":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Mint a merchant-scoped session token (raw token control)","description":"For custom UIs and mobile SDKs that want the token without pre-built portal URLs. The token authenticates as `X-Session-Token` and can only access this one merchant.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"ttlSeconds":{"type":"integer","minimum":60,"maximum":86400,"default":3600},"use":{"type":"string","enum":["portal","chat","rest"],"default":"portal"}}}}}},"responses":{"200":{"description":"Session token","content":{"application/json":{"schema":{"type":"object","properties":{"sessionToken":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"},"ttlSeconds":{"type":"integer"},"merchantId":{"type":"string"},"use":{"type":"string"}}}}}}}}},"/v1/merchants/{merchantId}/payment-methods/{pmId}":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}},{"name":"pmId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Fetch one payment method","responses":{"200":{"description":"The payment method"},"404":{"description":"Not found"}}},"put":{"summary":"Update a payment method","description":"Patch-style: send only the fields you are changing. Updating `description` is the normal way to re-run a determination against better wording.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"channel":{"type":"string"},"vendor":{"type":"string"}}}}}},"responses":{"200":{"description":"The updated payment method"},"404":{"description":"Not found"}}}},"/v1/merchants/{merchantId}/payment-methods/{pmId}/determine":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}},{"name":"pmId","in":"path","required":true,"schema":{"type":"string"}},{"name":"wait","in":"query","schema":{"type":"string","enum":["1"]},"description":"If set, sync wrapper (max 27s); otherwise async with jobId"}],"post":{"summary":"AI determines SAQ type from natural-language description","description":"Default mode is async — returns a 202 with jobId + pollUrl. Polls take 8–60s typically. Pass `?wait=1` for synchronous (bounded at 27s).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["description"],"properties":{"description":{"type":"string","minLength":10}}}}}},"responses":{"200":{"description":"Sync result (if ?wait=1)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeterminationResult"}}}},"202":{"description":"Async ack","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobAck"}}}}}}},"/v1/merchants/{merchantId}/payment-methods/{pmId}/followup":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}},{"name":"pmId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Answer the AI's follow-up question for SAQ determination","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["answer","threadId"],"properties":{"answer":{"type":"string"},"threadId":{"type":"string"}}}}}},"responses":{"202":{"description":"Async ack","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobAck"}}}}}}},"/v1/jobs/determine/{jobId}":{"parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"summary":"Poll a determination job","responses":{"200":{"description":"Status update","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatus"}}}}}}},"/v1/merchants/{merchantId}/attempts":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Open a new SAQ attempt (returns applicable section list)","description":"Opens a new attempt for a payment method and preloads the list of PCI DSS top-level requirements that apply to the determined SAQ type. SAQ A typically has 7 sections (2,3,6,8,9,11,12); SAQ D has all 12. Iterate `applicableTopLevels` in order — never assume 1-12 all apply.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["paymentMethodId","saqType"],"properties":{"paymentMethodId":{"type":"string"},"saqType":{"type":"string"}}}}}},"responses":{"201":{"description":"Created — attempt + applicableSections","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/SAQAttempt"},{"type":"object","properties":{"applicableSections":{"type":"array","items":{"type":"object","properties":{"topLevel":{"type":"string"},"title":{"type":"string"},"childCount":{"type":"integer"}}}},"applicableTopLevels":{"type":"array","items":{"type":"string"}},"firstTopLevel":{"type":"string"}}}]}}}}}},"get":{"summary":"List attempts for a merchant (each item carries `renewable`)","responses":{"200":{"description":"List"}}}},"/v1/merchants/{merchantId}/attempts/{attemptId}/renew":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}},{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"},"description":"A previously SEALED attempt to renew from"}],"post":{"summary":"Annual renewal: new attempt with last year's answers carried forward","description":"Creates a fresh attempt for the same payment method + SAQ type, copying last year's answers as pre-filled (flagged `carriedForward`) responses. Returns a `renewal` diff (carriedCount / newControls / droppedControls) showing whether the PCI question set changed since last year. The merchant must still re-confirm answers, complete the review step, and sign fresh before the renewal can seal — signatures are never carried. Errors: 409 RENEWAL_SOURCE_NOT_SEALED, 409 RENEWAL_ALREADY_OPEN (open attempt for the payment method), 404.","responses":{"201":{"description":"New attempt + renewal diff + ordered nextSteps"}}}},"/v1/partner":{"get":{"summary":"Own partner profile (tier, test-mode, webhook config, billing summary)","responses":{"200":{"description":"Profile — no secrets"}}}},"/v1/partner/webhook":{"put":{"summary":"Set or clear your webhook receive URL","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["webhookUrl"],"properties":{"webhookUrl":{"type":"string","nullable":true,"format":"uri"}}}}}},"responses":{"200":{"description":"Updated"}}}},"/v1/partner/webhook-test":{"post":{"summary":"Send a signed `ping` webhook to your configured URL and return the delivery result","responses":{"200":{"description":"Delivered"},"502":{"description":"Delivery failed — attempts included"}}}},"/v1/partner/webhook/rotate-secret":{"post":{"summary":"Rotate the webhook HMAC secret (returned once; also invalidates outstanding session tokens)","responses":{"200":{"description":"New secret — shown once"}}}},"/v1/partner/rotate-key":{"post":{"summary":"Rotate your API key (returned once; old key stays valid 24 h for zero-downtime rollover)","responses":{"200":{"description":"New key — shown once"}}}},"/v1/partner/billing":{"get":{"summary":"Own billing state: status, pending usage this cycle, recent invoices, Stripe portal link","responses":{"200":{"description":"Billing view"}}}},"/v1/merchants/{merchantId}/attempts/{attemptId}/sections":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}},{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"List section-level (parent) groupings + child progress","description":"Returns one entry per applicable PCI DSS top-level requirement with the leaf children grouped under it. Use this for the parent-flow Step 2 UX.","responses":{"200":{"description":"Section grouping + per-section progress"}}}},"/v1/merchants/{merchantId}/attempts/{attemptId}/sections/{topLevel}/combined":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}},{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"}},{"name":"topLevel","in":"path","required":true,"schema":{"type":"string"},"description":"PCI DSS requirement number (\"1\" through \"12\")"}],"post":{"summary":"Generate ONE combined AI question for an entire requirement (async)","description":"Returns a 202 jobAck. Poll /v1/jobs/section/:jobId?kind=combined for the combined question + 3 typed suggested answers (In Place / In Place with Compensating Control / Not Applicable).","responses":{"202":{"description":"Async ack","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobAck"}}}}}}},"/v1/merchants/{merchantId}/attempts/{attemptId}/sections/{topLevel}/analyze":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}},{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"}},{"name":"topLevel","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Submit ONE combined answer for an entire requirement (async)","description":"The AI distributes the merchant's combined answer across every leaf control under this topLevel and returns per-child compliance status, reason, and suggestion.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string","description":"The merchant's combined natural-language answer"}}}}}},"responses":{"202":{"description":"Async ack","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobAck"}}}}}}},"/v1/jobs/section/{jobId}":{"parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"attemptId","in":"query","schema":{"type":"string"}},{"name":"topLevel","in":"query","schema":{"type":"string"}},{"name":"kind","in":"query","schema":{"type":"string","enum":["combined","analyze"]}}],"get":{"summary":"Poll a section combined-question or analyze job","responses":{"200":{"description":"Status update"}}}},"/v1/merchants/{merchantId}/attempts/{attemptId}/review":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}},{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"List AoC review groups + signature state","description":"Returns the consolidated review questions (company info, business address, acquirer, locations, business description; plus an optional remediation group when any control was flagged Not In Place). Walk the merchant through each.","responses":{"200":{"description":"Review groups state"}}},"post":{"summary":"Record one review-group answer","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["groupId","answer"],"properties":{"groupId":{"type":"string","enum":["company_info","business_address","acquirer","locations","business_description","remediation"]},"answer":{"type":"string"}}}}}},"responses":{"200":{"description":"Updated review answers + remaining count"}}}},"/v1/merchants/{merchantId}/attempts/{attemptId}/sign":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}},{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Capture digital signature on the SAQ + AoC","description":"Records signatoryName, signatoryTitle, acknowledgement text, and signedAt timestamp. Required before /finalize. The signature is rendered in cursive (Caveat) on the AoC attestation page with a UTC timestamp underneath.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["signatoryName","signatoryTitle","acknowledgement"],"properties":{"signatoryName":{"type":"string","description":"Full legal name of the merchant signatory"},"signatoryTitle":{"type":"string","description":"Title (e.g., \"Owner\", \"Director of Finance\")"},"acknowledgement":{"type":"string","description":"Typed attestation text"}}}}}},"responses":{"200":{"description":"Signature recorded"}}}},"/v1/merchants/{merchantId}/attempts/{attemptId}/finalize":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}},{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Finalize: gate-check, seal, generate official PCI form-filled SAQ + AoC PDFs, email","description":"Three gates: (1) signature exists, (2) all required review groups answered, (3) section completeness. If any leaf control was missed by the parent-flow distribution, finalize auto-inherits the parent topLevel's answer + status before sealing. Then runs the SaaS prefill orchestrator (one OpenAI call per page, 3-8 minutes) and Fill_SAQ_PDF to stamp the official PCI templates. Returns permanent download URLs that point at our authenticated document-proxy endpoints — both the partner API key and a merchant session token work, and the URLs do not expire.","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"deliveryEmail":{"type":"string","description":"Email address for the finished PDFs (defaults to merchant.email)"},"allowIncomplete":{"type":"boolean","description":"Last-resort override when a control truly cannot be answered. Avoid in production."}}}}}},"responses":{"200":{"description":"Sealed + URLs","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["sealed"]},"saqType":{"type":"string"},"attemptId":{"type":"string"},"breakdown":{"type":"object","properties":{"inPlace":{"type":"integer"},"notInPlace":{"type":"integer"},"notApplicable":{"type":"integer"},"compensating":{"type":"integer"},"unanswered":{"type":"integer"}}},"signature":{"type":"object"},"reviewAnswers":{"type":"array"},"pdf":{"type":"object","properties":{"saqUrl":{"type":"string","format":"uri","description":"Permanent auth-gated download URL."},"aocUrl":{"type":"string","format":"uri","description":"Permanent auth-gated download URL."},"pipeline":{"type":"string","enum":["saas-prefill","pdf-lib-fallback"]}}},"orgCompliant":{"type":"boolean"}}}}}},"400":{"description":"NO_SIGNATURE / MISSING_REVIEW / INCOMPLETE"},"409":{"description":"ALREADY_SEALED"}}}},"/v1/merchants/{merchantId}/attempts/{attemptId}/documents/{kind}.pdf":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}},{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"}},{"name":"kind","in":"path","required":true,"schema":{"type":"string","enum":["saq","aoc"]}},{"name":"disposition","in":"query","required":false,"schema":{"type":"string","enum":["attachment","inline"]},"description":"Defaults to attachment (download)."}],"get":{"summary":"Download the sealed SAQ or AoC PDF (permanent, auth-gated)","description":"Streams the finalized PDF straight from S3. The URL does not expire — auth is checked on every request. Works with either a partner API key (server-side) or a merchant session token (browser). Returns 404 NOT_SEALED until /finalize completes.","responses":{"200":{"description":"PDF stream","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"NOT_SEALED / UNKNOWN_DOC / PDF_NOT_FOUND"}}}},"/v1/merchants/{merchantId}/attempts/{attemptId}":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}},{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Fetch one SAQ attempt","description":"Includes `status`, `saqType`, and — once finalize has sealed the documents — `documentKeys`. Polling this is how you learn a long-running finalize has completed.","responses":{"200":{"description":"The attempt"},"404":{"description":"Not found"}}}},"/v1/merchants/{merchantId}/attempts/{attemptId}/questions":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}},{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"summary":"Get all questions + section progress for an attempt","description":"Returns leaf controls only (parents with descendants are filtered). Each question carries `topLevel` + `sectionTitle` for client-side grouping.","responses":{"200":{"description":"Questions + section progress","content":{"application/json":{"schema":{"type":"object","properties":{"attempt":{"$ref":"#/components/schemas/SAQAttempt"},"progress":{"type":"object","properties":{"total":{"type":"integer"},"answered":{"type":"integer"},"percentage":{"type":"integer"}}},"sections":{"type":"array","items":{"$ref":"#/components/schemas/Section"}},"questions":{"type":"array","items":{"$ref":"#/components/schemas/Question"}}}}}}}}}},"/v1/merchants/{merchantId}/attempts/{attemptId}/questions/{controlId}/combined":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}},{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"}},{"name":"controlId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Generate AI combined question + suggested answers (async)","responses":{"202":{"description":"Async ack","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobAck"}}}}}}},"/v1/merchants/{merchantId}/attempts/{attemptId}/questions/{controlId}/analyze":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}},{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"}},{"name":"controlId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Submit answer and get AI compliance analysis (async)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["answer"],"properties":{"answer":{"type":"string"},"status":{"type":"string","enum":["In Place","Not In Place","Not Applicable","In Place with Compensating Control"]},"remediationPlan":{"type":"string"},"remediationTargetDate":{"type":"string"}}}}}},"responses":{"202":{"description":"Async ack","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobAck"}}}}}}},"/v1/jobs/saq-control/{jobId}":{"parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"attemptId","in":"query","schema":{"type":"string"}},{"name":"controlId","in":"query","schema":{"type":"string"}},{"name":"kind","in":"query","schema":{"type":"string","enum":["combined","analyze"]}}],"get":{"summary":"Poll a combined-question or analyze job","responses":{"200":{"description":"Status update","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatus"}}}}}}},"/v1/merchants/{merchantId}/attempts/{attemptId}/submit":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}},{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Submit completed SAQ to bank, seal attempt, kick off PDF + email","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"userEmail":{"type":"string"}}}}}},"responses":{"200":{"description":"Submission summary"},"400":{"description":"INCOMPLETE — list of unanswered controls"},"409":{"description":"ALREADY_SUBMITTED"}}}},"/v1/merchants/{merchantId}/attempts/{attemptId}/seal":{"parameters":[{"name":"merchantId","in":"path","required":true,"schema":{"type":"string"}},{"name":"attemptId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"summary":"Trigger PDF generation + email delivery (without re-sealing)","responses":{"200":{"description":"Seal kickoff result"}}}},"/v1/portfolio":{"get":{"summary":"Partner-wide compliance rollup for the bank/ISO/PayFac dashboard","description":"One partner-scoped snapshot: `totals` by effective status, a `complianceRate` (0–1, compliant & current over the whole book), a `funnel` of mutually-exclusive stages from never-invited → compliant, a `saqTypeBreakdown` (count keyed by the merchant's most-recent SAQ type), and the soonest-expiring attestations. Derived from merchant records only — no per-merchant attempt scans. Pages through the entire book. Partner API key only.","responses":{"200":{"description":"Portfolio rollup","content":{"application/json":{"schema":{"type":"object","properties":{"totals":{"type":"object","properties":{"merchants":{"type":"integer"},"compliant":{"type":"integer"},"expired":{"type":"integer"},"expiringSoon":{"type":"integer"},"inProgress":{"type":"integer"},"notStarted":{"type":"integer"}}},"funnel":{"type":"object","description":"Mutually-exclusive lifecycle stages that sum to totalMerchants.","properties":{"totalMerchants":{"type":"integer"},"neverInvited":{"type":"integer"},"invited":{"type":"integer"},"started":{"type":"integer"},"inProgress":{"type":"integer"},"completed":{"type":"integer"},"expiringSoon":{"type":"integer"},"expired":{"type":"integer"}}},"saqTypeBreakdown":{"type":"object","additionalProperties":{"type":"integer"},"description":"Merchant count keyed by SAQ type (only merchants with a sealed SAQ)."},"complianceRate":{"type":"number","description":"0–1; compliant + expiring-soon over the whole book."},"expiringSoonWindowDays":{"type":"integer"},"expiringSoon":{"type":"array","items":{"type":"object","properties":{"merchantId":{"type":"string"},"name":{"type":"string"},"expiresAt":{"type":"string","format":"date-time"},"daysUntilExpiry":{"type":"integer"},"saqType":{"type":"string","nullable":true}}}},"generatedAt":{"type":"string","format":"date-time"}}}}}}}}},"/v1/portfolio/export.csv":{"get":{"summary":"Download the full merchant book as CSV (Excel / examiner export)","description":"Streams one row per merchant with columns: merchantId, externalMerchantId, name, email, effectiveStatus, lastSaqType, compliantAt, expiresAt, daysUntilExpiry, invitedAt, inviteCount, createdAt. Sends `Content-Disposition: attachment`. Partner API key only — a merchant-scoped session token is rejected with 403 PARTNER_KEY_REQUIRED.","responses":{"200":{"description":"CSV file","content":{"text/csv":{"schema":{"type":"string","format":"binary"}}}},"403":{"description":"PARTNER_KEY_REQUIRED — called with a session token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/v1/audit":{"get":{"summary":"Query your audit trail (SOC 2 / SOX / dispute evidence)","description":"Newest-first, partition-scoped to your partner so you can never see another tenant's events. Filter by `merchantId` (served from the byMerchant index, then re-scoped to your partner), `eventType`, and a `since`/`until` ISO window. Cursor-paginate with `nextToken`.","parameters":[{"name":"merchantId","in":"query","schema":{"type":"string"},"description":"Restrict to a single merchant (per-merchant drill-down)."},{"name":"eventType","in":"query","schema":{"type":"string"},"description":"e.g. attempt.sealed, merchant.created, attempt.signed."},{"name":"since","in":"query","schema":{"type":"string","format":"date-time"},"description":"Only events at/after this ISO timestamp."},{"name":"until","in":"query","schema":{"type":"string","format":"date-time"},"description":"Only events at/before this ISO timestamp."},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":1000,"default":100}},{"name":"nextToken","in":"query","schema":{"type":"string"},"description":"Opaque pagination cursor from a prior response."}],"responses":{"200":{"description":"Audit events (newest first)","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","properties":{"timestamp":{"type":"string","format":"date-time"},"eventType":{"type":"string"},"actor":{"type":"string"},"resourceType":{"type":"string"},"resourceId":{"type":"string"},"merchantId":{"type":"string"},"attemptId":{"type":"string"},"metadata":{"type":"object","additionalProperties":true}}}},"nextToken":{"type":"string","nullable":true}}}}}}}}},"/mcp":{"post":{"summary":"MCP JSON-RPC endpoint (initialize, tools/list, tools/call, resources/read, prompts/get)","description":"Standard Model Context Protocol — see /docs#mcp for the tool catalog.","responses":{"200":{"description":"JSON-RPC response"}}}}}}