Docs · Data and retention
Docs · Data and retention
Send the minimum. We keep less.
What Attestiq needs per draft, what it refuses, what it stores, and the controls your admin has over storage and retention.
Data contract
What to send per draft
Send the draft and only the records it was written from. Everything else stays in your systems. Each record is {type, id, data}; amounts are integer cents, dates are YYYY-MM-DD.
| Workload | Required records | Also used |
|---|---|---|
| disputes.representment | transaction, chargeback | representment (response date, evidence list) |
| disputes.cardholder_letter | transaction, dispute | — |
| authorize.retry_policy | diagnosis | — |
| authorize.recommendation | — | — |
A missing required record type returns 422 invalid_request naming it. For chargeback responses, send the representment record too: the evidence and deadline checks read it.
Chargeback responses
| Record | Fields | Notes |
|---|---|---|
| transaction | amount_cents, currency, transaction_date, network, card_type, card_last4, merchant_name | No card number. cardholder_name is optional; if sent, it is tokenized before storage. |
| chargeback | network, reason_code, category (fraud, not_received, …), chargeback_date, amount_cents | |
| representment | response_date, evidence (list: three_ds_authenticated, avs_full_match, cvv2_match, ani_match, device_match, prior_undisputed_transactions, proof_of_delivery, proof_of_service) | The evidence list is what the evidence check verifies the draft against. |
| risk_signals (optional) | 3DS result, AVS, CVV2, ANI, device match, prior undisputed count | Kept for the reviewer. Derive the evidence list from it on your side. |
| Draft (output) | The AI-drafted response, as text or JSON | Stored only as its SHA-256 and, in redacted mode, a redacted copy. |
Cardholder dispute letters
| Record | Fields |
|---|---|
| transaction | amount_cents, currency, transaction_date, merchant_name, network, card_type, card_last4 |
| dispute | network, reason_code, category, claim_received_date, disputed_amount_cents, provisional_credit_cents, provisional_credit_date, outcome, resolution_date, letter_date, merchant_delivery_date |
Data contract
Never send
- Full card numbers (PAN). Send the last four, and the BIN if you need it.
- CVV or any card security code.
- Social Security numbers.
- Full postal addresses.
- Bank account numbers.
- Passwords, API keys, or tokens.
Card numbers are blocked by code (next section). The rest is your responsibility: map only the fields in the data contract.
Card numbers
Card numbers are rejected, not stored
In records: any Luhn-valid 13–19 digit number, anywhere in records, rejects the whole request with 422 pci_data_rejected. The number is not logged. The same guard covers uploaded eval cases.
In the draft: a card number your agent wrote into the draft is a critical finding, PRIVACY.PAN_EXPOSED, so the verdict is fail. The number is masked before storage in the text, the findings, and the evidence. In async mode it is masked before the job is queued.
This is designed to keep Attestiq out of your cardholder-data environment. Your QSA decides PCI scope.
# Any Luhn-valid 13–19 digit card number anywhere in "records" —
# a whole value, inside a text field, or a JSON number — rejects the request.
HTTP/1.1 422 Unprocessable Entity
{"code": "pci_data_rejected",
"message": "a record contains a full card number; send BIN + last four only"}Personal data
Redaction
Names, emails, phone numbers, addresses, and IP addresses are redacted before storage, logs, and any model call. Known values from your records are removed first (a cardholder name in a record is removed from the draft), then a detector catches the rest. Record fields named like personal data are replaced with salted tokens such as TOK_15669dbc56b5.
Amounts, dates, codes, merchant ids, BIN, and last four are kept: the checks need them.
Webhook payloads and audit events are scrubbed the same way. Models, when enabled, see only redacted text. No customer data trains any model.
Storage
What is stored
Each validation shows its mode in storage_mode. The mode is fixed when the validation is created; changing the setting affects new validations only.
output_hash is the SHA-256 of the draft exactly as you sent it, in sync and async mode alike, so you can match it against your own copy.
// GET /v1/validate/validations/{id} (storage_mode "redacted")
{
"output_hash": "sha256:ccf28b9c…", // fingerprint of the draft as sent
"output_redacted": "September 15, 2026\n\nDear <PERSON>,\n\nThank you for contacting us about
the charge to your debit card ending in 4417. ...",
"records": [
{"type": "dispute", "id": "DSP-2026-0918", "data": {
"reason_code": "13.1", "outcome": "no_error", ...,
"cardholder_name": "TOK_15669dbc56b5"}} // name-like fields are tokenized
],
"findings": [{"code": "FACT.AMOUNT_MISMATCH",
"quote": "... a purchase of $68.40 at Harbor Outfitters ...",
"expected": "$86.40", "observed": "$68.40", ...}]
}| Item | redacted mode (default) | minimal mode |
|---|---|---|
| The original draft | Never. Only its SHA-256 | Never. Only its SHA-256 |
| Redacted draft | Stored | Not stored |
| Records | Stored, redacted and tokenized | Not stored |
| Finding quotes | Stored, redacted | Not stored |
| Verdict, score, finding codes, expected / observed values | Stored | Stored |
| Evidence records | Hashes, record references, versions; finding and claim evidence include the redacted quotes | Hashes, record references, versions; no quotes |
| Audit events | Who did what and when; personal data scrubbed | Same |
| Async job payload | Redacted before queueing; purged when the job finishes | Same |
| Card numbers | Never | Never |
Storage
Storage modes
redacted (default) keeps the redacted draft, redacted records, and redacted quotes, so a reviewer can open the draft in the console at the sentence that is wrong.
minimal stores no draft text, no records, and no quotes, anywhere. What remains is the SHA-256, the verdict, the finding codes with expected and observed values, and the evidence hashes. Reviewers open the draft in your own system, using external_id.
Every change is written to the audit chain with its before and after values. Only the admin role has tenant:admin.
# Read (tenant:read — every user role; not the service key)
curl https://api.attestiq.ai/v1/settings/data -H "X-API-Key: $ATTESTIQ_USER_KEY"
{"storage_mode": "redacted", "retention_days": 365, "last_sweep_at": null}
# Change (admin key, tenant:admin). Both fields optional.
curl -X PUT https://api.attestiq.ai/v1/settings/data \
-H "X-API-Key: $ATTESTIQ_ADMIN_KEY" -H "Content-Type: application/json" \
-d '{"storage_mode": "minimal", "retention_days": 90}'
{"storage_mode": "minimal", "retention_days": 90, "last_sweep_at": null}
# retention_days outside 7..3650
HTTP/1.1 422 Unprocessable Entity
{"code": "invalid_request", "message": "request validation failed",
"errors": [{"loc": ["body", "retention_days"],
"msg": "Input should be greater than or equal to 7"}]}| Endpoint | Permission | Returns |
|---|---|---|
| GET /v1/settings/data | tenant:read | {storage_mode, retention_days, last_sweep_at} |
| PUT /v1/settings/data | tenant:admin | Same shape. Body: storage_mode (redacted | minimal) and/or retention_days (7–3650) |
Storage
Retention and deletion
Retention. Stored content (redacted draft, records, quotes) older than retention_days is purged by a daily sweep. The default is 365 days. An admin can also run the sweep at once with POST /v1/settings/data/sweep; the response counts the validations purged.
Deletion on request. DELETE /v1/validate/validations/{id}/content (admin) purges one validation's stored content now and sets content_purged_at.
What stays after either. The validation record, its output_hash, verdict, score, finding codes with expected and observed values, the evidence hashes, and the audit chain. That is the record that the check happened, and it still verifies (GET /v1/validate/validations/{id}/verify). Both actions are audited.
Evidence is write-once
Evidence records are never edited or deleted, so purging does not touch them. In redacted mode, the claim and finding evidence records include the redacted quotes; those remain after a purge. If quotes must never be kept, use minimal mode from the start.
# Run the retention sweep now (it also runs daily)
curl -X POST https://api.attestiq.ai/v1/settings/data/sweep -H "X-API-Key: $ATTESTIQ_ADMIN_KEY"
{"purged": 12, "by_module": {"validate": 12}, "cutoff": "2026-06-20T16:42:37Z"}
# Delete one validation's stored content on request
curl -X DELETE https://api.attestiq.ai/v1/validate/validations/val_01a0b7f933cf2ee003dce6c2/content \
-H "X-API-Key: $ATTESTIQ_ADMIN_KEY"
{"id": "val_01a0b7f933cf2ee003dce6c2", "content_purged_at": "2026-09-18T16:42:37Z"}| Endpoint | Permission | Returns |
|---|---|---|
| POST /v1/settings/data/sweep | tenant:admin | {purged, by_module, cutoff} |
| DELETE /v1/validate/validations/{id}/content | tenant:admin | {id, content_purged_at} |
Deployment
Deployment options
| Option | What leaves your boundary | Status |
|---|---|---|
| Attestiq cloud (multi-tenant, US AWS region) | The draft and the minimum records, over TLS | Planned. Code built; hosted environment not yet live |
| Dedicated (single-tenant VPC we run) | Same, on isolated infrastructure and keys | Planned. Not yet live |
| In your AWS account (your VPC, your Bedrock) | Nothing. You run it; we ship the software and policy packs | Planned. Same code; packaging and runbook still to do |
Also planned before the first production tenant: encryption at rest with KMS, write-once evidence storage (S3 Object Lock), private networking and WAF, database row-level security, SSO, and a penetration test. The current state of each control is on the security page.