The invoice generator
While you are filling in the form, nothing leaves your browser. The invoice is held in browser storage under the key paperpony.invoice-generator.v1 so a reload does not lose your work, and the logo you upload is read locally and never uploaded on its own. Clearing site data removes both. There is no account, so there is nothing for any of it to be attached to.
When you press download, the invoice is posted once to a Cloudflare Worker of ours, which passes it to the renderer. The renderer produces the PDF and puts it in a private bucket behind a signed link. That stored copy lives for one hour, and a sweep that runs every fifteen minutes deletes it from the bucket and then clears the row that pointed at it.
One hour rather than the thirty days the underlying plan allows, because the documents are yours and the purpose ends when your browser has the file.
What the row keeps after that is the fact of the render and not the invoice: status, size, page count, timings, and the page settings we rendered with. What it stops keeping, in the same statement that deletes the file, is the filename and the running header and footer, because those carried your invoice number. A render that failed has no file to delete, so the same clearing runs against it on the same hour by a different path.
Downloads are limited to ten an hour from one connection so the tool stays available. Cloudflare gives the Worker your address, the Worker turns it into an identifier for a counter, and the counter holds a list of timestamps. The address itself is not written anywhere.
Asking for an API key
The form on the key page stores four things: the email address you type, the sentence about what you are building, the address the request came from, and the time. They go into a table in our database and are also emailed to us. The free-text field is never written to a log.
The address is kept for one reason, which is telling a person apart from a script. If you want the row deleted, write to hello@paperpony.dev and it is deleted.
A request is kept for twelve months and then deleted, whether or not it was answered. The same sweep that clears expired files does it, on the same schedule, so the period is something that happens rather than something written down here.
If you have an API key
The account row holds your email address, a name, the plan and the credit counters. Keys are stored as an HMAC and a twelve-character prefix, never as the key itself, which is why a lost key is replaced rather than recovered.
Each render writes a job row. That row is metadata about the render and not the document: status, byte size, page count, credits charged, the timings, the object key, and the options you sent. The HTML you sent and the data you sent are not stored. A stored template is the exception and is obvious: its source is the thing you asked us to keep, so we keep it until you archive it.
Three of those options are your text rather than a setting: the filename, and the header and footer templates that print on every page. They are cleared when your output is deleted, in the same statement, so they live exactly as long as the file did and not a sweep longer. The rest of the options describe how we rendered rather than what, and those stay.
One place holds the document itself, briefly. A render is queued in Redis with the payload attached; a job that finishes drops it immediately, and a job that fails keeps it for fifteen minutes so the failure can be looked at, then it goes too.
Rendered files live for the retention on your plan, which is 24 hours on Free, seven days on Starter and thirty on Growth and Scale. The same sweep deletes them.
What the logs contain
One line per request, carrying the request id, the account id, the route, the method, the status code, the duration and whether it succeeded. No query string, no headers, no body.
That is the application's log. The web server in front of the API keeps its own, which is the ordinary one every web server keeps: the calling IP address, the request line, the response size, the referrer and the user agent. It is rotated daily and fourteen days are kept. Naming it here because the paragraph above would otherwise read as the whole account of what is written down, and it is not.
On top of that the logger is configured to blank out a list of field names wherever they appear, including authorization, data, html, source, key, secret and password, and to rewrite anything shaped like an API key. A rendered document has never been in a log line and the redaction is the second lock rather than the first.
Who else touches it
Cloudflare serves this site, runs the Worker behind the invoice generator, and stores both the rendered objects and the daily database backup. Hetzner runs the machine with the API, the renderer and the database. Zoho carries the mail from the key form. That is the whole list.
The database is dumped once a day and a dump is kept for thirty days, in the same EU bucket, read only to restore. So a row deleted from the live database, by your request or by one of the sweeps above, can still exist in a backup for up to thirty days after it goes. Saying so because the word “deleted” on this page otherwise implies something faster than the truth.
There is no analytics script on this site. The code can load Cloudflare Web Analytics if a token is configured, and no token is configured, so nothing is loaded. There are no cookies at all, from us or from anyone else. The fonts are served from our own origin rather than from a font CDN, which is the same decision made for the same reason.
Error reporting to Sentry is wired into the API and the renderer and is switched off: with no DSN set the client is never started. If it is ever turned on, the request body, headers, cookies and query string are stripped before anything is sent.
What we do not do
We do not sell data, share it with advertisers, or use anything you send to train a model of any kind. Your documents are rendered and then deleted on the schedule above. There is no secondary use.
Who holds it
YTI Digital OÜ, registered in Estonia under number 17298015, VAT EE102925876, in Tallinn. The R2 bucket holding rendered files is set to the EU jurisdiction, so objects stay in the European Union.
The API, the renderer and the database run on one Hetzner machine in Nuremberg, Germany. Nothing of yours is processed outside the European Union.
Deletion and questions
Write to hello@paperpony.dev. That reaches a person, and it is the address for a deletion request, a copy of what is held, or a question about any of the above.