{"openapi":"3.1.0","info":{"title":"Crosstabs Public Aggregate Statistics API","version":"1.0.0","description":"Existing free, accountless endpoints for aggregate integer frequency counts. No respondent-row upload or private project APIs are defined here. All inferential tables require 2–12 rows and columns, rectangular shape, positive row/column margins, and grand total ≤ 1000000000000. Fractional/weighted cells are rejected. These are ordinary independent-frequency methods, not survey-design inference. Source: lib/api/contingency-handler.ts and app/api/v1 route handlers. Unknown /api paths return HTTP 404 JSON with error, code=endpoint_not_found, hint and documentation; HEAD returns the same status without a body. API schema version is separate from application and PyPI package versions. Public REST v1 compatibility policy: breaking changes use a new major URL prefix rather than silently changing /api/v1. Additive response fields may be introduced; clients should tolerate unknown fields. Current v1 endpoints are not deprecated and have no scheduled sunset. If a deprecation is scheduled, the public changelog and developer documentation will publish the affected endpoints, migration guidance and planned dates. This policy is not an availability SLA or a promise of indefinite service. See https://www.crosstabs.com/changelog and https://www.crosstabs.com/developers for published changes and migration guidance.","contact":{"name":"Crosstabs support","email":"support@crosstabs.com","url":"https://www.crosstabs.com/support"}},"servers":[{"url":"https://www.crosstabs.com"}],"security":[],"externalDocs":{"description":"Examples, statistical limits, MCP and local CLI setup","url":"https://www.crosstabs.com/developers"},"x-crosstabs-interfaces":{"description":"Informational Crosstabs extension, not an agent-manifest standard. MCP uses its own initialization, tools/list and tools/call protocol rather than the REST schemas below.","openapi":"https://www.crosstabs.com/openapi.json","documentation":"https://www.crosstabs.com/developers","publicAggregateMcp":{"url":"https://www.crosstabs.com/api/headless-mcp","transport":"Streamable HTTP","toolCount":5,"tools":["search","fetch","get_headless_capabilities","analyze_crosstab","render_crosstab"],"maxPostBytes":16384,"requestsPerMinutePerProcessClient":60,"dataBoundary":"Fixed public evidence and bounded aggregate counts only; no respondent rows or private projects."},"hostedMcp":{"url":"https://mcp.crosstabs.com/mcp","transport":"Streamable HTTP","toolCount":46,"dataBoundary":"Separate service: uploads require consent; private operations require a workspace token; access expires after 24 hours. Discover the live tool catalog with tools/list."},"mcpSetup":"https://www.crosstabs.com/mcp","localPackage":"https://pypi.org/project/crosstabs/1.4.0/","evidence":"https://www.crosstabs.com/evidence.json","localCli":{"package":"crosstabs==1.4.0","install":"pip install crosstabs==1.4.0","command":"crosstabs-headless","transport":"stdio","dataBoundary":"Local project processing; the AI client has its own data policies."}},"paths":{"/api/v1/contingency":{"get":{"operationId":"getContingency","summary":"Analyze an aggregate contingency table","tags":["Aggregate statistics"],"description":"The query is transmitted to the server and may appear in browser history or infrastructure logs. Send aggregate counts only; use POST when you want to keep counts out of URLs. GET label parameters accepted by the shared table codec do not alter statistics and are not echoed in responses.","parameters":[{"name":"d","in":"query","required":true,"description":"Rectangular integer frequency matrix: commas separate cells; semicolons separate rows. Same bounds and margin rules as TableRequest. Example: 20,30;30,20.","schema":{"type":"string"},"example":"20,30;30,20"}],"responses":{"200":{"description":"Computed aggregate analysis. Non-2×2 tables return null for inapplicable exact/odds-ratio results, not an HTTP error. Check unavailable reasons and notes before interpreting.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContingencyResponse"}}}},"400":{"description":"Invalid or missing table/JSON, fractional counts, fewer than two rows or columns, non-positive margins, or bounds exceeded. The JSON error string explains how to correct the request.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"POST request body exceeds 16,384 bytes, checked while reading the body.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"In-process shared limiter exceeded: 30 requests/minute per client IP across these four routes. Serverless instances do not share counters; cold starts reset counters and infrastructure can impose additional limits.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}},"Retry-After":{"description":"Seconds to wait before retrying; matches RateLimit-Reset.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Calculation produced a non-finite response.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"postContingency","summary":"Analyze an aggregate contingency table","tags":["Aggregate statistics"],"requestBody":{"required":true,"description":"JSON aggregate counts only. Maximum encoded request body: 16,384 bytes. Extra object properties are ignored by the implementation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TableRequest"},"example":{"table":[[20,30],[30,20]]}}}},"responses":{"200":{"description":"Computed aggregate analysis. Non-2×2 tables return null for inapplicable exact/odds-ratio results, not an HTTP error. Check unavailable reasons and notes before interpreting.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContingencyResponse"}}}},"400":{"description":"Invalid or missing table/JSON, fractional counts, fewer than two rows or columns, non-positive margins, or bounds exceeded. The JSON error string explains how to correct the request.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"POST request body exceeds 16,384 bytes, checked while reading the body.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"In-process shared limiter exceeded: 30 requests/minute per client IP across these four routes. Serverless instances do not share counters; cold starts reset counters and infrastructure can impose additional limits.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}},"Retry-After":{"description":"Seconds to wait before retrying; matches RateLimit-Reset.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Calculation produced a non-finite response.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"options":{"operationId":"optionsContingency","summary":"Read CORS preflight policy","responses":{"204":{"description":"Empty response. Allows any origin, GET/POST/OPTIONS, and the Content-Type request header.","headers":{"Access-Control-Allow-Origin":{"schema":{"type":"string","const":"*"}},"Access-Control-Allow-Methods":{"schema":{"type":"string","const":"GET, POST, OPTIONS"}},"Access-Control-Allow-Headers":{"schema":{"type":"string","const":"Content-Type"}}}}}}},"/api/v1/chi-square":{"get":{"operationId":"getChiSquare","summary":"Calculate Pearson chi-square, Yates correction, G-test and Cramér’s V","tags":["Aggregate statistics"],"description":"The query is transmitted to the server and may appear in browser history or infrastructure logs. Send aggregate counts only; use POST when you want to keep counts out of URLs. GET label parameters accepted by the shared table codec do not alter statistics and are not echoed in responses.","parameters":[{"name":"d","in":"query","required":true,"description":"Rectangular integer frequency matrix: commas separate cells; semicolons separate rows. Same bounds and margin rules as TableRequest. Example: 20,30;30,20.","schema":{"type":"string"},"example":"20,30;30,20"}],"responses":{"200":{"description":"Computed aggregate analysis. Non-2×2 tables return null for inapplicable exact/odds-ratio results, not an HTTP error. Check unavailable reasons and notes before interpreting.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChiSquareResponse"}}}},"400":{"description":"Invalid or missing table/JSON, fractional counts, fewer than two rows or columns, non-positive margins, or bounds exceeded. The JSON error string explains how to correct the request.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"POST request body exceeds 16,384 bytes, checked while reading the body.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"In-process shared limiter exceeded: 30 requests/minute per client IP across these four routes. Serverless instances do not share counters; cold starts reset counters and infrastructure can impose additional limits.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}},"Retry-After":{"description":"Seconds to wait before retrying; matches RateLimit-Reset.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Calculation produced a non-finite response.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"postChiSquare","summary":"Calculate Pearson chi-square, Yates correction, G-test and Cramér’s V","tags":["Aggregate statistics"],"requestBody":{"required":true,"description":"JSON aggregate counts only. Maximum encoded request body: 16,384 bytes. Extra object properties are ignored by the implementation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TableRequest"},"example":{"table":[[20,30],[30,20]]}}}},"responses":{"200":{"description":"Computed aggregate analysis. Non-2×2 tables return null for inapplicable exact/odds-ratio results, not an HTTP error. Check unavailable reasons and notes before interpreting.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChiSquareResponse"}}}},"400":{"description":"Invalid or missing table/JSON, fractional counts, fewer than two rows or columns, non-positive margins, or bounds exceeded. The JSON error string explains how to correct the request.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"POST request body exceeds 16,384 bytes, checked while reading the body.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"In-process shared limiter exceeded: 30 requests/minute per client IP across these four routes. Serverless instances do not share counters; cold starts reset counters and infrastructure can impose additional limits.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}},"Retry-After":{"description":"Seconds to wait before retrying; matches RateLimit-Reset.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Calculation produced a non-finite response.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"options":{"operationId":"optionsChiSquare","summary":"Read CORS preflight policy","responses":{"204":{"description":"Empty response. Allows any origin, GET/POST/OPTIONS, and the Content-Type request header.","headers":{"Access-Control-Allow-Origin":{"schema":{"type":"string","const":"*"}},"Access-Control-Allow-Methods":{"schema":{"type":"string","const":"GET, POST, OPTIONS"}},"Access-Control-Allow-Headers":{"schema":{"type":"string","const":"Content-Type"}}}}}}},"/api/v1/fishers-exact":{"get":{"operationId":"getFishersExact","summary":"Calculate a two-sided Fisher–Irwin exact test for a 2×2 table","tags":["Aggregate statistics"],"description":"The query is transmitted to the server and may appear in browser history or infrastructure logs. Send aggregate counts only; use POST when you want to keep counts out of URLs. GET label parameters accepted by the shared table codec do not alter statistics and are not echoed in responses.","parameters":[{"name":"d","in":"query","required":true,"description":"Rectangular integer frequency matrix: commas separate cells; semicolons separate rows. Same bounds and margin rules as TableRequest. Example: 20,30;30,20.","schema":{"type":"string"},"example":"20,30;30,20"}],"responses":{"200":{"description":"Computed aggregate analysis. Non-2×2 tables return null for inapplicable exact/odds-ratio results, not an HTTP error. Check unavailable reasons and notes before interpreting.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FisherResponse"}}}},"400":{"description":"Invalid or missing table/JSON, fractional counts, fewer than two rows or columns, non-positive margins, or bounds exceeded. The JSON error string explains how to correct the request.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"POST request body exceeds 16,384 bytes, checked while reading the body.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Fixed-margin support exceeds 100000 tables. Preserves the analysis response with fishersExact=null and unavailableReason=workload_limit, plus error, code and hint for recovery.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FisherWorkloadError"}}}},"429":{"description":"In-process shared limiter exceeded: 30 requests/minute per client IP across these four routes. Serverless instances do not share counters; cold starts reset counters and infrastructure can impose additional limits.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}},"Retry-After":{"description":"Seconds to wait before retrying; matches RateLimit-Reset.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Calculation produced a non-finite response.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"postFishersExact","summary":"Calculate a two-sided Fisher–Irwin exact test for a 2×2 table","tags":["Aggregate statistics"],"requestBody":{"required":true,"description":"JSON aggregate counts only. Maximum encoded request body: 16,384 bytes. Extra object properties are ignored by the implementation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TableRequest"},"example":{"table":[[20,30],[30,20]]}}}},"responses":{"200":{"description":"Computed aggregate analysis. Non-2×2 tables return null for inapplicable exact/odds-ratio results, not an HTTP error. Check unavailable reasons and notes before interpreting.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FisherResponse"}}}},"400":{"description":"Invalid or missing table/JSON, fractional counts, fewer than two rows or columns, non-positive margins, or bounds exceeded. The JSON error string explains how to correct the request.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"POST request body exceeds 16,384 bytes, checked while reading the body.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Fixed-margin support exceeds 100000 tables. Preserves the analysis response with fishersExact=null and unavailableReason=workload_limit, plus error, code and hint for recovery.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FisherWorkloadError"}}}},"429":{"description":"In-process shared limiter exceeded: 30 requests/minute per client IP across these four routes. Serverless instances do not share counters; cold starts reset counters and infrastructure can impose additional limits.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}},"Retry-After":{"description":"Seconds to wait before retrying; matches RateLimit-Reset.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Calculation produced a non-finite response.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"options":{"operationId":"optionsFishersExact","summary":"Read CORS preflight policy","responses":{"204":{"description":"Empty response. Allows any origin, GET/POST/OPTIONS, and the Content-Type request header.","headers":{"Access-Control-Allow-Origin":{"schema":{"type":"string","const":"*"}},"Access-Control-Allow-Methods":{"schema":{"type":"string","const":"GET, POST, OPTIONS"}},"Access-Control-Allow-Headers":{"schema":{"type":"string","const":"Content-Type"}}}}}}},"/api/v1/odds-ratio":{"get":{"operationId":"getOddsRatio","summary":"Calculate a sample odds ratio and Woolf 95% interval for a 2×2 table","tags":["Aggregate statistics"],"description":"The query is transmitted to the server and may appear in browser history or infrastructure logs. Send aggregate counts only; use POST when you want to keep counts out of URLs. GET label parameters accepted by the shared table codec do not alter statistics and are not echoed in responses.","parameters":[{"name":"d","in":"query","required":true,"description":"Rectangular integer frequency matrix: commas separate cells; semicolons separate rows. Same bounds and margin rules as TableRequest. Example: 20,30;30,20.","schema":{"type":"string"},"example":"20,30;30,20"}],"responses":{"200":{"description":"Computed aggregate analysis. Non-2×2 tables return null for inapplicable exact/odds-ratio results, not an HTTP error. Check unavailable reasons and notes before interpreting.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OddsRatioResponse"}}}},"400":{"description":"Invalid or missing table/JSON, fractional counts, fewer than two rows or columns, non-positive margins, or bounds exceeded. The JSON error string explains how to correct the request.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"POST request body exceeds 16,384 bytes, checked while reading the body.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"In-process shared limiter exceeded: 30 requests/minute per client IP across these four routes. Serverless instances do not share counters; cold starts reset counters and infrastructure can impose additional limits.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}},"Retry-After":{"description":"Seconds to wait before retrying; matches RateLimit-Reset.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Calculation produced a non-finite response.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"operationId":"postOddsRatio","summary":"Calculate a sample odds ratio and Woolf 95% interval for a 2×2 table","tags":["Aggregate statistics"],"requestBody":{"required":true,"description":"JSON aggregate counts only. Maximum encoded request body: 16,384 bytes. Extra object properties are ignored by the implementation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TableRequest"},"example":{"table":[[20,30],[30,20]]}}}},"responses":{"200":{"description":"Computed aggregate analysis. Non-2×2 tables return null for inapplicable exact/odds-ratio results, not an HTTP error. Check unavailable reasons and notes before interpreting.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OddsRatioResponse"}}}},"400":{"description":"Invalid or missing table/JSON, fractional counts, fewer than two rows or columns, non-positive margins, or bounds exceeded. The JSON error string explains how to correct the request.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"POST request body exceeds 16,384 bytes, checked while reading the body.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"In-process shared limiter exceeded: 30 requests/minute per client IP across these four routes. Serverless instances do not share counters; cold starts reset counters and infrastructure can impose additional limits.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}},"Retry-After":{"description":"Seconds to wait before retrying; matches RateLimit-Reset.","schema":{"type":"integer","minimum":1}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Calculation produced a non-finite response.","headers":{"RateLimit-Limit":{"description":"Per-process client quota over a 60-second sliding window.","schema":{"type":"integer","const":30}},"RateLimit-Remaining":{"description":"Remaining requests after this request.","schema":{"type":"integer","minimum":0,"maximum":29}},"RateLimit-Reset":{"description":"Ceiling seconds until the oldest retained hit expires and the next slot becomes available; not an epoch timestamp or a full-quota reset.","schema":{"type":"integer","minimum":0}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"options":{"operationId":"optionsOddsRatio","summary":"Read CORS preflight policy","responses":{"204":{"description":"Empty response. Allows any origin, GET/POST/OPTIONS, and the Content-Type request header.","headers":{"Access-Control-Allow-Origin":{"schema":{"type":"string","const":"*"}},"Access-Control-Allow-Methods":{"schema":{"type":"string","const":"GET, POST, OPTIONS"}},"Access-Control-Allow-Headers":{"schema":{"type":"string","const":"Content-Type"}}}}}}},"/api/health":{"get":{"operationId":"getHealth","summary":"Read application and known-answer statistics self-check","tags":["Operations"],"responses":{"200":{"description":"Operational. Trust-readiness pending evidence is separate and does not mark runtime health degraded.","headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store, max-age=0"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthReport"}}}},"503":{"description":"Runtime or statistics self-check degraded.","headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store, max-age=0"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthReport"}}}}}},"head":{"operationId":"headHealth","summary":"Read health status without a response body","tags":["Operations"],"responses":{"200":{"description":"Operational, empty body.","headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store, max-age=0"}}}},"503":{"description":"Degraded, empty body.","headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store, max-age=0"}}}}}}}},"components":{"schemas":{"FrequencyMatrix":{"type":"array","minItems":2,"maxItems":12,"items":{"type":"array","minItems":2,"maxItems":12,"items":{"type":"integer","minimum":0,"maximum":1000000000000}},"description":"Additional runtime constraints: rectangular rows; grand total ≤ 1000000000000; every row and column sum must be positive. Ordinary JSON Schema cannot enforce these cross-cell sums.","x-grand-total-maximum":1000000000000},"TableRequest":{"type":"object","properties":{"table":{"$ref":"#/components/schemas/FrequencyMatrix"}},"required":["table"]},"Error":{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string","enum":["invalid_query","body_too_large","invalid_json","invalid_table","invalid_counts","insufficient_dimensions","empty_margins","rate_limit_exceeded","calculation_failed"]},"hint":{"type":"string"}},"required":["error","code","hint"]},"FisherWorkloadError":{"allOf":[{"$ref":"#/components/schemas/FisherResponse"},{"type":"object","properties":{"error":{"type":"string"},"code":{"type":"string","const":"fisher_workload_limit"},"hint":{"type":"string"}},"required":["error","code","hint"]}]},"Input":{"type":"object","properties":{"table":{"$ref":"#/components/schemas/FrequencyMatrix"},"n":{"type":"integer","minimum":1,"maximum":1000000000000},"rows":{"type":"integer","minimum":2,"maximum":12},"cols":{"type":"integer","minimum":2,"maximum":12}},"required":["table","n","rows","cols"]},"ChiSquareResponse":{"type":"object","properties":{"input":{"$ref":"#/components/schemas/Input"},"docs":{"type":"string","const":"https://www.crosstabs.com/developers"},"chiSquare":{"type":"object","properties":{"chiSquare":{"type":"number"},"degreesOfFreedom":{"type":"integer"},"pValue":{"type":"number","minimum":0,"maximum":1}},"required":["chiSquare","degreesOfFreedom","pValue"]},"chiSquareYates":{"anyOf":[{"type":"object","properties":{"chiSquare":{"type":"number"},"degreesOfFreedom":{"type":"integer"},"pValue":{"type":"number","minimum":0,"maximum":1}},"required":["chiSquare","degreesOfFreedom","pValue"]},{"type":"null"}]},"gTest":{"type":"object","properties":{"gStatistic":{"type":"number"},"degreesOfFreedom":{"type":"integer"},"pValue":{"type":"number","minimum":0,"maximum":1}},"required":["gStatistic","degreesOfFreedom","pValue"]},"cramersV":{"type":"object","properties":{"value":{"type":"number"},"label":{"type":"string","enum":["negligible","small","medium","large"]}},"required":["value","label"]},"expected":{"type":"object","properties":{"min":{"type":"number"},"cellsBelow5":{"type":"integer"}},"required":["min","cellsBelow5"]},"warning":{"type":["string","null"]}},"required":["input","docs","chiSquare","chiSquareYates","gTest","cramersV","expected","warning"]},"FisherResponse":{"type":"object","properties":{"input":{"$ref":"#/components/schemas/Input"},"docs":{"type":"string","const":"https://www.crosstabs.com/developers"},"fishersExact":{"anyOf":[{"type":"object","properties":{"pValue":{"type":"number","minimum":0,"maximum":1},"oddsRatio":{"type":["number","null"]},"oddsRatioState":{"type":"string","enum":["finite","positive_infinity"]},"oddsRatioCI":{"type":"array","items":{"type":["number","null"]},"minItems":2,"maxItems":2},"oddsRatioCIStates":{"type":"array","items":{"type":"string","enum":["finite","positive_infinity"]},"minItems":2,"maxItems":2}},"required":["pValue","oddsRatio","oddsRatioState","oddsRatioCI","oddsRatioCIStates"]},{"type":"null"}]},"unavailableReason":{"type":["string","null"],"enum":[null,"not_2x2","invalid_counts","unsafe_integer_counts","non_positive_margins","workload_limit","disabled_by_caller"]},"note":{"type":["string","null"]}},"required":["input","docs","fishersExact","unavailableReason","note"]},"OddsRatioResponse":{"type":"object","properties":{"input":{"$ref":"#/components/schemas/Input"},"docs":{"type":"string","const":"https://www.crosstabs.com/developers"},"oddsRatio":{"anyOf":[{"type":"object","properties":{"value":{"type":["number","null"]},"valueState":{"type":"string","enum":["finite","positive_infinity"]},"ci95":{"type":"array","items":{"type":["number","null"]},"minItems":2,"maxItems":2},"ci95States":{"type":"array","items":{"type":"string","enum":["finite","positive_infinity"]},"minItems":2,"maxItems":2},"method":{"type":"string","const":"sample odds ratio with Woolf log interval"}},"required":["value","valueState","ci95","ci95States","method"]},{"type":"null"}]},"fishersExactP":{"anyOf":[{"type":"number","minimum":0,"maximum":1},{"type":"null"}]},"fishersExactUnavailableReason":{"type":["string","null"],"enum":[null,"not_2x2","invalid_counts","unsafe_integer_counts","non_positive_margins","workload_limit","disabled_by_caller"]},"note":{"type":["string","null"]}},"required":["input","docs","oddsRatio","fishersExactP","fishersExactUnavailableReason","note"]},"ContingencyResponse":{"type":"object","properties":{"input":{"$ref":"#/components/schemas/Input"},"docs":{"type":"string","const":"https://www.crosstabs.com/developers"},"chiSquare":{"type":"object","properties":{"chiSquare":{"type":"number"},"degreesOfFreedom":{"type":"integer"},"pValue":{"type":"number","minimum":0,"maximum":1}},"required":["chiSquare","degreesOfFreedom","pValue"]},"chiSquareYates":{"anyOf":[{"type":"object","properties":{"chiSquare":{"type":"number"},"degreesOfFreedom":{"type":"integer"},"pValue":{"type":"number","minimum":0,"maximum":1}},"required":["chiSquare","degreesOfFreedom","pValue"]},{"type":"null"}]},"gTest":{"type":"object","properties":{"gStatistic":{"type":"number"},"degreesOfFreedom":{"type":"integer"},"pValue":{"type":"number","minimum":0,"maximum":1}},"required":["gStatistic","degreesOfFreedom","pValue"]},"fishersExact":{"anyOf":[{"type":"object","properties":{"pValue":{"type":"number","minimum":0,"maximum":1},"oddsRatio":{"type":["number","null"]},"oddsRatioState":{"type":"string","enum":["finite","positive_infinity"]},"oddsRatioCI":{"type":"array","items":{"type":["number","null"]},"minItems":2,"maxItems":2},"oddsRatioCIStates":{"type":"array","items":{"type":"string","enum":["finite","positive_infinity"]},"minItems":2,"maxItems":2}},"required":["pValue","oddsRatio","oddsRatioState","oddsRatioCI","oddsRatioCIStates"]},{"type":"null"}]},"fishersExactUnavailableReason":{"type":["string","null"],"enum":[null,"not_2x2","invalid_counts","unsafe_integer_counts","non_positive_margins","workload_limit","disabled_by_caller"]},"cramersV":{"type":"object","properties":{"value":{"type":"number"},"label":{"type":"string","enum":["negligible","small","medium","large"]}},"required":["value","label"]},"phi":{"type":["number","null"]},"contingencyCoefficient":{"type":"number"},"lambda":{"type":"object","properties":{"symmetric":{"type":"number"},"rowDependent":{"type":"number"},"colDependent":{"type":"number"}},"required":["symmetric","rowDependent","colDependent"]},"gamma":{"type":"number"},"kendallTau":{"type":"object","properties":{"tauB":{"type":"number"},"tauC":{"type":"number"}},"required":["tauB","tauC"]},"somersD":{"type":"object","properties":{"symmetric":{"type":"number"},"rowDependent":{"type":"number"},"colDependent":{"type":"number"}},"required":["symmetric","rowDependent","colDependent"]},"theilsU":{"type":"object","properties":{"symmetric":{"type":"number"},"rowGivenCol":{"type":"number"},"colGivenRow":{"type":"number"}},"required":["symmetric","rowGivenCol","colGivenRow"]},"expected":{"type":"object","properties":{"counts":{"type":"array","items":{"type":"array","items":{"type":"number"}}},"min":{"type":"number"},"cellsBelow5":{"type":"integer"}},"required":["counts","min","cellsBelow5"]},"totals":{"type":"object","properties":{"row":{"type":"array","items":{"type":"number"}},"col":{"type":"array","items":{"type":"number"}}},"required":["row","col"]}},"required":["input","docs","chiSquare","chiSquareYates","gTest","fishersExact","fishersExactUnavailableReason","cramersV","phi","contingencyCoefficient","lambda","gamma","kendallTau","somersD","theilsU","expected","totals"]},"HealthReport":{"type":"object","properties":{"schemaVersion":{"type":"integer","const":3},"service":{"type":"string","const":"crosstabs.com"},"status":{"type":"string","enum":["operational","degraded"]},"checkedAt":{"type":"string","format":"date-time"},"build":{"type":"object","properties":{"deploymentId":{"type":["string","null"]},"deploymentUrl":{"type":["string","null"]},"sourceRevision":{"type":["string","null"]},"sourceBranch":{"type":["string","null"]},"applicationVersion":{"type":"string"},"provenance":{"type":"object","properties":{"confidence":{"type":"string","enum":["metadata_only","unverified"]},"limitations":{"type":"array","items":{"type":"string"}}},"required":["confidence","limitations"]}},"required":["deploymentId","deploymentUrl","sourceRevision","sourceBranch","applicationVersion","provenance"]},"trustReadiness":{"type":"object","properties":{"status":{"type":"string","enum":["pending","evidence_recorded"]},"affectsOperationalStatus":{"type":"boolean","const":false},"basis":{"type":"string","const":"checked-in-release-contract"},"contractUpdatedAt":{"type":"string"},"pendingEvidence":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"detail":{"type":"string"}},"required":["id","name","detail"]}},"limitations":{"type":"array","items":{"type":"string"}}},"required":["status","affectsOperationalStatus","basis","contractUpdatedAt","pendingEvidence","limitations"]},"checks":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","enum":["application_runtime","statistics_engine"]},"name":{"type":"string"},"status":{"type":"string","enum":["operational","degraded"]},"detail":{"type":"string"}},"required":["id","name","status","detail"]}}},"required":["schemaVersion","service","status","checkedAt","build","trustReadiness","checks"]}}}}