Trust & Security

Last updated: October 29, 2026

On this page
  1. Rate-limited by design
  2. Every action is audited, and the log checks itself
  3. …and you can check it without trusting us
  4. API versioning that won’t break you
  5. Real read-only access
  6. Evidence is tamper-evident
  7. Payment webhooks are verified
  8. Your alert webhooks are resilient, not just fired once
  9. Every response carries a correlation id
  10. Repeat a call safely with Idempotency-Key
  11. Session-authenticated actions are rate-limited too
  12. Sessions can be revoked
  13. Deletion is actually complete
  14. What we keep of your data, stated plainly
  15. Where it runs
  16. What we don’t have yet
  17. Report a security issue

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.

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.

Every action is audited, and the log checks 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.

…and you can check it without trusting us

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.

Run the check yourself

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 f791d83aaf40fb1109dd7f205489c9cdacdb9b0f4de0308748afd81f77ad993f

Download the verifier

The 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

f791d83aaf40fb1109dd7f205489c9cdacdb9b0f4de0308748afd81f77ad993f

Rotating 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

369af39130e5a2b1da679f19069b6d597eb20b539dcef3414713424650d160b5

Retired — still published, so checkpoints it signed stay verifiable · Key ID 7975ab855e47457b

acfe841ececd3349c2409e056b57cf7582f55bad0ffd5f153cb941e1fc57b7b5

What 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.

API versioning that won’t break you

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.

Real read-only access

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.

Evidence is tamper-evident

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.

Payment webhooks are verified

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.

Your alert webhooks are resilient, not just fired once

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.

Every response carries a correlation id

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.

Repeat a call safely with Idempotency-Key

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.

Session-authenticated actions are rate-limited too

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.

Sessions can be revoked

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.

Deletion is actually complete

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.

What we keep of your data, stated plainly

Only what Kirelta needs to keep scoring your data:

  • Fitted models: summary statistics of your baseline and, for each column on its own, the values set aside to calibrate it, sorted. Not your rows. A model is kept until you refit it under the same name or delete your account.
  • Two exceptions, because these models score live data against the data itself: a baseline whose values fall into separate groups keeps a rescaled copy of part of it, and output monitoring keeps the model outputs you sent with the baseline, rescaled.
  • CSV uploads, so you can rebuild from them: your most recent per model, by plan (Free 1, Pro 5, Business 20, Enterprise 50). Older ones are deleted automatically.
  • Background fits (?async=1): the rows wait in a queue until the fit finishes or fails, and are then deleted.
  • Retried requests (Idempotency-Key): the response, not your rows, for 10 minutes, so a retry gets the same answer.
  • Readings and decisions: results and statistics, never the rows behind them. Readings are kept for 90 days.

Beyond this, rows sent to /fit or /assess are not kept once the response is sent.

Where it runs

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.

What we don’t have yet

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.

Report a security issue

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.