Audits
Audits
An audit compares a synthetic panel with human data, or, with no human file, ranks its questions by how likely they are to diverge.
Endpoints
| Method and path | What it does |
|---|---|
POST /v1/audits | Create an audit. Returns 202 and the job. |
GET /v1/audits/{id} | The job. result is present once it has succeeded. |
GET /v1/audits/{id}/report.html | The report as a web page. |
GET /v1/audits/{id}/report.pdf | The report as a PDF. |
GET /v1/audits/{id}/questions.csv | The per-question table. |
DELETE /v1/audits/{id} | Remove the result from the live system now. Returns {"id": "...", "deleted": true}. A ks_test_ key cannot delete a live audit (403 live_key_required). |
GET /v1/audits/{id}/verification | The signed verification record. See Verification. |
Create an audit
Send multipart/form-data.
| Field | Required | Meaning |
|---|---|---|
synthetic_file | yes | The synthetic panel: CSV, SPSS .sav or Excel. |
human_file | no | Human responses to the same questionnaire. Without it the audit ranks questions only and nothing is observed: the bins are review priorities (see The result) and no Kernsignal Verified record can be issued for it. |
config | no | A JSON string, described below. |
Optional header Idempotency-Key: repeating a request with the same key returns the original job instead of creating a second one.
config
| Key | Type | Default | Meaning |
|---|---|---|---|
human_weight | string or null | null | Weight column in the human file. |
synthetic_weight | string or null | null | Weight column in the synthetic file. |
subgroups | array of strings | [] | Up to 5 columns present in both files. Each level is audited separately. |
exclude | array of strings | [] | Extra column names that are never scored. Demographic and screening columns are already excluded by name (see below); use this for coded columns such as Q17 that hold age or region. A value that is not an array of strings is 400 invalid_config. |
min_cell_n | integer | 50 | Minimum human sample for a verdict on a question or subgroup cell. |
seed | integer | 7 | Seed for the noise simulation, so a re-run gives the same numbers. |
webhook | boolean | false | Send a webhook when this audit finishes. |
The job object
{
"id": "aud_...",
"object": "audit",
"status": "queued",
"created_at": "2026-11-14T10:00:00Z",
"started_at": null,
"finished_at": null,
"mode": "test",
"config": {"human_weight": "weight", "subgroups": ["gender"]},
"result": null,
"error": null
}
status moves queued, running, then succeeded or failed. Timestamps are ISO 8601 in UTC with a trailing Z. mode is live or test, from the key. On failure error holds the same envelope fields as an error response.
The result
When an audit has succeeded, result.summary.bins counts the questions in each bin. result.mode is paired when at least one question was compared with human answers and ranker_only otherwise (no human file, or no question with enough human answers). The keys below are the same in both modes; result.bins holds the labels for the mode.
| Bin | Meaning |
|---|---|
use | Use the synthetic answers. |
spot_check | Check a sample with humans before relying on them. |
field | Ask humans. |
Without human evidence
In ranker_only mode nothing was observed, so the bins are review priorities and never say the synthetic data can be used: field is Check first, spot_check is Check next and use is Lower priority. The report, questions.csv and result.bins use those labels. The bins are rank shares of the ranker score (the top 20% and the next 40% of questions), not a measured divergence rate. An audit in this mode gets no verification record: GET /v1/audits/{id}/verification returns 400 verification_requires_human_evidence, and the report says why.
Excluded columns
Columns that describe respondents rather than ask a question are not scored: age, gender or sex, region, state or province, education, income, race or ethnicity, party identification, weights and respondent IDs, matched by column name as whole words (age_band and household_income match, state_of_economy does not). The columns named in subgroups, the weight columns and everything in config.exclude are excluded too. Each one is listed in result.excluded and in result.warnings with the message excluded: demographic/screening variable "<column>" (code variable_excluded).
result.summary.bins_by_evidence splits each bin into questions judged against human answers (observed) and questions placed by the synthetic-only ranker (ranker). A ranker score is a ranking, not a probability. The report lists the reasoning, the subgroup cells, the follow-up sample sizes and the limits of the method.
Retention and deletion
Uploaded files are never stored. A result stays in the live system for 30 days (24 hours for ks_test_ keys) or until you delete it, and is removed from the live system immediately when you delete it. Nightly backups hold deleted results for up to 14 days. Verification records persist until revoked. A SHA-256 hash of each uploaded file is kept for the verification record.