Evidence exports
An export gives your compliance team a file of acceptance records for a period, for an audit, a dispute or a regulator. You create an export with a filter, Propper freezes the matching records at that moment, and you download the result as a CSV or JSONL file.
Every call needs a click:export token.
The scope is standalone: click:admin, click:write and click:read do not include it, so request it explicitly (see OAuth scopes).
A user token carries it only when the user's role can export evidence.
| Resource | Purpose |
|---|---|
POST /v1/click/exports | Create an export. |
GET /v1/click/exports | List your organization's exports, newest first. |
GET /v1/click/exports/{id} | Read an export's status, counts and file list. |
GET /v1/click/exports/{id}/files/{name} | Download one file. |
DELETE /v1/click/exports/{id} | Delete an export. Its files then answer 410. |
The base path is https://api.propper.ai/v1/click/exports.
Create an export
curl -X POST https://api.propper.ai/v1/click/exports \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: 5b0f6c1e-0d2a-4c53-9e0f-3f6c2a7d1b10" \
-d '{
"format": "csv",
"filters": {
"from": "2026-01-01T00:00:00Z",
"to": "2026-10-01T00:00:00Z",
"environment": "PRODUCTION",
"outcome": "ACCEPTED"
}
}'
| Field | Description |
|---|---|
format | Required. csv or jsonl. |
filters.from, to | Required, inclusive, at most 366 days apart. |
filters.templateId | Only acceptances of this template. |
filters.versionId | Only acceptances of this template version. With templateId, the version must belong to that template. |
filters.environment | PRODUCTION or SANDBOX. |
filters.outcome | ACCEPTED, DECLINED or EXPIRED. |
filters.signerRef | Only acceptances by this signer, an exact match on the reference you sent when recording them. |
restrictedAccess | Optional { "reason", "claimRef" }. Includes records under a legal restriction. See below. |
includeEvidence | Not available yet. Sending true is a 400 CLICK_VALIDATION_FAILED. |
The filter list is closed.
An unknown key, including q, is a 400 CLICK_VALIDATION_FAILED.
signerRef in the bodyA signerRef filter goes in the POST body and nowhere else.
A query string reaches load-balancer and proxy logs, so a signer reference there would be written to logs outside Propper's control.
The export is frozen to the matching acceptances when it is created and the reference is then discarded: the export stores only that a signer filter was used, and never returns the value.
Restricted records
Without restrictedAccess, records under a legal restriction are left out, and counts.restrictedExcluded says how many.
To include them, send a reason (DISPUTE_DEFENSE, DISPUTE_ESTABLISH, COURT_ORDER or REGULATOR_REQUEST) and a claimRef, an identifier for the matter of 1 to 128 letters, digits and . _ : / -, starting with a letter or digit.
Free text in either field is a 400.
Both are recorded in the audit log.
The response
The API answers 202 Accepted with a Location: /v1/click/exports/{id} header and the job:
{
"data": {
"id": "0d3b6f0a-5f2e-4a5c-9a38-2c1e7b4d9f10",
"status": "READY",
"format": "csv",
"includeEvidence": false,
"filters": {
"from": "2026-01-01T00:00:00Z",
"to": "2026-10-01T00:00:00Z",
"environment": "PRODUCTION",
"outcome": "ACCEPTED"
},
"asOf": "2026-10-29T17:02:11Z",
"rowCount": 1240,
"counts": { "accepted": 1240, "declined": 0, "expired": 0, "restrictedExcluded": 0 },
"files": [{ "name": "rows.csv", "contentType": "text/csv", "rows": 1240 }],
"createdAt": "2026-10-29T17:02:11Z",
"expiresAt": "2026-11-05T17:02:11Z"
}
}
asOf is the moment the records were frozen: acceptances recorded later are not in the export.
Send an X-Idempotency-Key so a retry after a timeout returns the same export instead of creating a second one.
The key is scoped to your client and the request body, so it cannot replay another client's export.
Status values
| Status | Meaning |
|---|---|
PENDING | Accepted, not started. |
PROCESSING | Being prepared. |
READY | Files can be downloaded. |
FAILED | Could not be prepared. Create a new export. |
EXPIRED | Past expiresAt. Files answer 410. |
DELETED | Deleted with DELETE. Files answer 410. |
VOIDED | Withdrawn because a signer it covers was erased. Files answer 410. |
An export is usually READY in the create response.
Poll GET /v1/click/exports/{id} until status is READY anyway, so your integration keeps working if preparation becomes asynchronous.
Download a file
curl -OJ https://api.propper.ai/v1/click/exports/0d3b6f0a-5f2e-4a5c-9a38-2c1e7b4d9f10/files/rows.csv \
-H "Authorization: Bearer $TOKEN"
files in the job lists the file names to ask for.
The body is streamed, never a link, so your token is checked on every download.
The response carries:
| Header | Value |
|---|---|
Content-Type | text/csv; charset=utf-8 or application/x-ndjson; charset=utf-8 |
Content-Disposition | attachment; filename="click-export-<id>-rows.csv" (.jsonl for JSONL) |
X-Export-Rows | The number of records in the file, frozen at create time. |
Cache-Control | private, no-store |
Check the download is whole before you rely on it:
- A failure after the first byte closes the connection instead of ending the body cleanly, so a short or broken download is always an error in your HTTP client, never a file that looks complete.
- A JSONL file ends with a line
{"type":"end","rows":N}. Count the record lines and compare withNandX-Export-Rows. - A CSV file has one header line followed by
X-Export-Rowsrecords.
Row columns
CSV columns and JSONL keys are the same, in this order.
| Column | Contents |
|---|---|
acceptanceId, receiptId | The acceptance and its receipt. |
outcome, acceptedAt, startedAt | What the signer did and when. |
templateId, templateName, versionId, version, versionLabel, revisionId, revisionNumber, deploymentId, environment | The agreement that was served. |
locale, consentMethod, evidenceTier, deviceClass | How the acceptance was made. |
signerRef | The signer reference you sent. erased: followed by 16 hex characters after an erasure. |
email | Empty for acceptances recorded before email was captured. Pseudonymized after an erasure. |
ipAddress | The signer's address. 0.0.0.0 after an erasure. |
identityErased | true once the signer's identity was erased. |
restricted | true for a record under a legal restriction (only present when you asked for those). |
formatVersion, servedChecksum, resolvedSettingsHash, chainSequence, previousChainHash, chainHash, signatureAlgorithm, signatureKeyId, signature | The tamper-evident chain and signature of the evidence bundle. |
evidenceStatus | State of the evidence bundle when the export was created. |
evidenceFiles | not_exported_v1 for now: evidence files are not part of an export yet. |
screenshot | Empty for now. Later sealed for acceptances that have a screenshot, which is never included in an export. |
consentChoices | The choices the signer made, as a list. |
The export never contains the raw user agent, merge data, captured page HTML or debug logs.
A CSV cell that starts with =, +, -, @, a tab or a carriage return is prefixed so a spreadsheet does not run it as a formula.
JSONL values are not altered.
Delete an export
DELETE /v1/click/exports/{id} answers 204.
It clears the stored filters at once, and every file of the export then answers 410 CLICK_EXPORT_GONE.
Exports are deleted for you 7 days after they were created.
Errors
Errors use the standard body.
| Status | errorCode | When |
|---|---|---|
| 400 | CLICK_VALIDATION_FAILED | An unknown or invalid filter, q, a window over 366 days, free text in restrictedAccess, or includeEvidence: true. |
| 401 | No or invalid token. | |
| 403 | The token does not carry click:export. | |
| 404 | CLICK_NOT_FOUND | The export, template or version is not your organization's, or the file name is not one of files. |
| 409 | CLICK_EXPORT_NOT_READY | A file was requested before the export was READY. |
| 410 | CLICK_EXPORT_GONE | The export expired, was deleted or was voided. |
| 422 | CLICK_EXPORT_TOO_LARGE | The filter matches more rows than one export allows. Narrow the window or add a filter. |
| 429 | CLICK_EXPORT_LIMIT_REACHED | A concurrency, daily or per-file limit was reached. Wait for Retry-After seconds. |
See Limits and retries for the values.
Not available yet
includeEvidence (evidence files in ZIP parts alongside the rows) is not available yet.
Until it is, evidenceFiles is not_exported_v1 and an export has one rows file.