Rate-limited by design
Signup and per-key request rates are capped server-side (5/hour on signup, per-key limits on assessment calls). This isn’t a gateway bolted on after the fact — it’s in the request path itself.
Last updated: October 29, 2026
This page exists because the people who evaluate Kirelta for a risk or fraud team ask about security before anything else — reasonably. Everything below is checked against the actual running code, the same way every other claim on this site is: measured, not asserted. Where something isn’t built yet, it says so.
Passwords and API keys are never stored in a form that could be read back. Passwords are salted and hashed before they touch the database. API keys are shown to you exactly once, at creation — Kirelta itself keeps only a SHA-256 hash of the key, the same way it keeps only a hash of the evidence behind a decision. If our database were copied in full, no password or key inside it could be used directly.
Signup and per-key request rates are capped server-side (5/hour on signup, per-key limits on assessment calls). This isn’t a gateway bolted on after the fact — it’s in the request path itself.
Key creation, revocation, team changes, sign-ins, and account deletion write to a hash-chained audit log per tenant — each entry seals in the one before it, so editing or deleting a past entry breaks every hash after it. That’s re-verified, not just displayed, every time you open Activity. This makes tampering detectable, not impossible: it’s tamper-evident, and we say so rather than overclaiming tamper-proof.
A hash chain stored in our database is only as strong as our database: anyone who could write to it could recompute the chain until it verified again. So we also sign periodic checkpoints over your log with an Ed25519 key that is never stored in that database. A rewritten log still verifies against itself — and stops matching a signature nobody can forge without the key.
Checkpoints are delivered to your own webhook as they’re made, so a copy lives in your systems, outside ours. Verify any time, offline, with tools/verify_audit_anchor.py — it imports none of our code, because a result you get by running our verifier against our data is a result you have to trust us for.
Save the body of GET /audit/anchor as anchor.json, then run the verifier with the current key. It needs Python 3 and nothing else.
curl -sO https://kirelta.com/tools/verify_audit_anchor.py
python3 verify_audit_anchor.py anchor.json --public-key f791d83aaf40fb1109dd7f205489c9cdacdb9b0f4de0308748afd81f77ad993fThe public key is below and at /.well-known/kirelta-audit-key. It can verify and cannot sign, which is why publishing it costs nothing — and why you should take it from here, on different infrastructure from the API you’re checking.
Current key · Key ID 61a2c481ba32fa31
f791d83aaf40fb1109dd7f205489c9cdacdb9b0f4de0308748afd81f77ad993fRotating a key never invalidates what it already signed, so retired keys stay published — a checkpoint you archived last year is still verifiable with the key that signed it. If you hold an export whose checkpoints name one of these, use it:
Retired — still published, so checkpoints it signed stay verifiable · Key ID b5fb4320568f2404
369af39130e5a2b1da679f19069b6d597eb20b539dcef3414713424650d160b5Retired — still published, so checkpoints it signed stay verifiable · Key ID 7975ab855e47457b
acfe841ececd3349c2409e056b57cf7582f55bad0ffd5f153cb941e1fc57b7b5What it doesn’t cover, plainly: entries written before your first checkpoint, and any written after the most recent one. Both are reported as uncovered rather than quietly counted as verified.
Every endpoint is reachable both unversioned and under /v1/ — pick either today, both keep working. Nothing gets deprecated out from under an existing integration without real notice first.
A workspace’s Viewer role isn’t a hidden button in the dashboard — the server itself rejects a Viewer’s key-creation or webhook-change request with a 403, even called directly against the API with no UI involved at all.
The evidence behind a recorded decision is sealed with a SHA-256 hash at the moment it’s written. Re-opening that decision later re-verifies the hash — if the stored evidence had been altered, the page would say so, not silently show it.
Billing events from Paddle are verified by their signature, and checked for freshness, before they change anything — so an event can be neither forged nor replayed.
A drift alert to your own webhook retries up to 3 times over a few seconds if your endpoint is briefly unreachable — a one-second network blip no longer means a silently dropped notification. A deliberate rejection from your endpoint (4xx) is not retried; that would not help.
An X-Request-Id header on every response, generated per request if none was already set upstream — or echoed back unchanged if your own gateway already assigned one. When something needs debugging, this is the one value that ties a customer’s report to the exact request in the logs.
Send an Idempotency-Key header with /fit or /assess, and a retry with the same key within 10 minutes gets the first response back instead of running — and being metered — a second time. The replay record is shared by every server process and survives a restart, so it holds even when the retry lands somewhere else.
A compromised session can’t be used to mint API keys, spam invitations, reconfigure your webhook, hammer the billing API, or flood decision transitions unbounded — each of these has its own independent hourly cap, separate from your plan’s own quotas, which exist for a different reason and shouldn’t be the only backstop.
Changing your password or using "sign out everywhere" invalidates every existing session token immediately, not just the one in front of you — enforced server-side via a version counter on the account, not a client-side flag.
Account deletion removes the account, its models, its keys, its uploads and its decision history. An automated test proves it leaves nothing behind: it finds every table that holds workspace data in the live schema, so a table added later is covered too. What remains is a receipt that a workspace was deleted: its internal id and the time, nothing of its content.
Only what Kirelta needs to keep scoring your data:
?async=1): the rows wait in a queue until the fit finishes or fails, and are then deleted.Idempotency-Key): the response, not your rows, for 10 minutes, so a retry gets the same answer.Beyond this, rows sent to /fit or /assess are not kept once the response is sent.
Application and database on Railway; DNS and the static site on Cloudflare; transactional email via Resend; billing via Paddle as merchant of record. Each sees only what it needs to do its one job. Full detail in the Privacy Policy.
No third-party security certification. No SOC 2, no ISO 27001. Those are real, expensive, multi-month processes, and claiming one without it would be exactly the kind of thing this page exists to avoid. If your organization requires a certification before onboarding, say so — it changes the timeline honestly, not the marketing.
No formal bug bounty program. If you find a real security issue, email our contact address directly — every report gets a real person’s attention, just not a bounty platform’s workflow yet.
Single-region deployment. No multi-region failover yet. Uptime history and incident disclosure will live here once there’s a real operating history to show, not a placeholder graph showing 100% from day one.
Email our contact address with what you found and how to reproduce it. You’ll get a response from the person who can actually act on it, not a ticket queue.