Retire the live book and mint a new identifier
For the day an identifier ends up on a spam list. COMPARE AND SWAP: you name the book you believe is live, and if it is not, this answers 409 COMPLAINT_BOOK_ROTATION_STALE and changes nothing. That is what makes a retry safe — without it, a repeated rotation would retire the identifier you had just published and your form would answer 404 with nothing to explain it. The retirement and the mint happen together, so there is no instant in which the company has no door. Complaints already filed under the old identifier keep their place: they are that company's legal record and retirement is a state, never a deletion.
For the day an identifier ends up on a spam list. COMPARE AND SWAP: you name the book you believe is live, and if it is not, this answers 409 COMPLAINT_BOOK_ROTATION_STALE and changes nothing. That is what makes a retry safe — without it, a repeated rotation would retire the identifier you had just published and your form would answer 404 with nothing to explain it. The retirement and the mint happen together, so there is no instant in which the company has no door. Complaints already filed under the old identifier keep their place: they are that company's legal record and retirement is a state, never a deletion.
Authorization
bearerAuth Tenant-bound, scoped and expiring Apifact credential. Migrated legacy credentials are accepted only on deprecated v1 writes and tenant-scoped v2 read, poll and download routes. Each operation names the single scope it requires in x-required-scope; the scope array of the security requirement itself is empty because OpenAPI 3.0 requires it to be for a non-oauth2 scheme.
In: header
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/api/v2/complaint-books/rotation" \ -H "Authorization: Bearer apf_v2_tu_credencial" \ -H "Content-Type: application/json" \ -d '{ "currentBookId": "3f7b1c22-9a41-4c8e-8f2d-6b5e0a91c7d4" }'{ "schemaVersion": "2.0", "requestId": "d385ab22-0f51-4b97-9ecd-b8ff3fd4fcb6", "bookId": "710266f4-0087-48f9-97e1-4b13ac48f353", "createdAt": "2019-08-24T14:15:22Z", "intakeUrl": "string"}Require a Turnstile token on this book's public door POST POST
The humanity check, and YOU turn it on. The widget lives on your page and we never see it; all this platform can do is verify the token it produces. IT IS OFF BY DEFAULT AND THAT IS DELIBERATE. Switching it on for everybody at deploy time would break every complaint form that exists, because none of them sends a token yet — and the failure would land on a consumer in the middle of filing a complaint. So it is off until you say your form is ready. ONCE ON, IT FAILS CLOSED. A door that demands the check and a deployment with no Turnstile secret bound answers 503 rather than accepting the complaint unchecked: you asked for protection, and serving a promise nobody is keeping is worse than saying so. A ROTATION CARRIES IT FORWARD. A fresh identifier with the check switched off would open the door at exactly the moment somebody rotated it, which is almost always the moment it was receiving spam. Turning it back off is allowed and easy on purpose: a company whose site key was rotated needs their legal complaint form to keep working while they fix it. Requires complaints:manage, the same capability as rotating, because the consequence is the same class — a form that stops working.
Read the complaints filed against this company GET GET
Returns complaints newest first, with the consumer's contact details, which the public receipt deliberately withholds. Requires its own scope: a credential that FILES complaints must not thereby be able to enumerate everybody who ever complained, because these are the personal data of people who are not your users and never chose to be in this system. pendingOnly=true returns only what has not been answered, which is the query that matters — the fifteen working day clock runs on those. respondedAt is null until somebody answers, and there is no stored deadline anywhere: it is derived from submittedAt, so it cannot drift from a calendar or from an answer recorded through another door.