Legal

Security

Last updated July 22, 2026

This page describes controls that exist in the product today. It is deliberately shorter than a security page you may be used to, because everything on it is something we can point at in the code — and Section 11 lists, by name, the claims we are not making. A security page that overstates is worse than one that is brief.

1. How to read this page

Enrichments is a small, independently operated product. We have chosen to describe mechanisms rather than programmes: what the software does on every request, not what an internal team is supposed to do on a schedule. Where a control is a design property enforced by code or by the database, we say so. Where something is absent, Section 11 says that instead of leaving you to infer it.

Nothing here is a warranty. No service is perfectly secure, and we do not claim to be.

2. Accounts and sessions

  • Passwords are stored only as a hash, produced by our authentication library. The plaintext is never persisted and we cannot read it. We are not naming a specific algorithm or parameters here, because we would only be repeating a library default rather than a setting we chose and verified.
  • Sessions are server-side records with an explicit expiry, identified by a token in an HTTP-only cookie. Each session records the IP address and user-agent it was created from.
  • A short-lived session cache avoids a database read on every request; it expires within minutes, so a revoked or expired session does not stay usable.
  • Sign-in through a third-party identity provider is optional and, when enabled, is used solely to authenticate you. We do not request access to any mailbox, file storage or other service you hold with that provider. The sign-in tokens it issues are stored in our database without an additional application-level encryption layer — we would rather state that than let a generic 'credentials are encrypted' line imply otherwise.
  • Password reset is self-service and link-based. The link is single-use and expires in an hour, resetting revokes every other session, and we email you a notice that the password changed — so a reset you did not ask for cannot happen silently. The reset form answers identically whether or not an account exists, so it cannot be used to find out who has one.
  • There is no multi-factor authentication in the product today.

3. API keys

  • A key is shown in full exactly once, at creation. We store a hash of it, along with its ek_live_ prefix and first few characters so you can identify it in a list. A lost key cannot be recovered — only replaced.
  • The hash is an unsalted SHA-256 digest, which is what makes constant-time lookup by key possible. We are naming the scheme rather than calling it 'strongly hashed': for a long random key generated by us this is a reasonable trade-off, and you should still treat a key as a secret and rotate it if exposed.
  • Keys belong to an organization, not to an individual, so a key survives a member leaving — and revoking a member does not silently break your integration.
  • Keys carry scoped permissions. A key without the required scope is refused with a 403 rather than a 401, so a permissions problem is not mistaken for an authentication problem.
  • Each key is rate-limited to 120 requests per 60 seconds. This is abuse protection, and is deliberately separate from credit metering.
  • Keys can be revoked or rotated at any time from the dashboard.

4. Tenancy and access scoping

Every session is bound to an organization at the moment it is created, so a request cannot execute without a tenant context. Reads and writes are scoped to that organization — deleting a conversation, for example, matches on the organization as well as the record, so a correct identifier from the wrong organization still finds nothing.

Your uploads, jobs, results, conversations and credits are organization-scoped. Your queriesare the exception, and we are naming it rather than letting the sentence above stand unqualified: upstream provider calls are cached in a table keyed by a hash of the request, that table has no organization column, and it stores the request as well as the response. The names, companies and domains you submit for enrichment are therefore held in a store shared across all customers, and one customer's cached lookup can serve another's identical query. That is deliberate — it is what makes a repeated question free — but it means "everything you send us is siloed" is not a claim we can make.

Our schema also contains canonical, globally deduplicated person and company tables. Nothing writes to them today and they are empty; enrichment results live in the per-organization job rows described above. Section 4 of our Privacy Policy sets out the whole storage model, including what it means for an erasure request.

5. Credit integrity, enforced by the database

The credit ledger is the product's financial record, and it is protected at the database layer rather than only in application code — a bug, a migration script or a direct database session cannot rewrite it.

  • Database rules make UPDATE and DELETE against the ledger do nothing at all. History cannot be edited, only appended to. Corrections are new adjustment rows that remain visible next to the original.
  • Database constraints reject a zero-value entry, reject a grant or purchase with a negative value and a usage entry with a positive one, and reject any usage row that cannot be attributed to a specific hold, job or message. An unattributable charge — the 'where did my credits go?' failure — cannot be written.
  • Your balance and every usage figure in the product are both computed by summing the same ledger rows. There is no second counter that can drift out of step with the first.
  • Work is reserved before it runs and settled after. A reservation must be positive, and a settled reservation must record when it settled, so a stranded hold is detectable by age rather than inferred.
  • Retried workflow steps are collapsed by a unique idempotency key, so an internal retry cannot double-charge.
  • A cache hit costs zero credits, and a run that returns no results releases its whole reservation.

6. What we deliberately do not collect

The Service never returns phone numbers, and this is enforced independently in six places: person records have no phone column at all; the field enumeration rejects every alias for one; the public API answers with a dedicated error code rather than a generic validation failure; phone columns in an uploaded CSV are never read, never sent upstream and never billed, and are passed through to your download untouched; the interface drops the field before rendering; and the unmodelled provider payload we keep alongside each record is scrubbed of phone-shaped keys, at every depth, before it is stored or served. Our pricing model omits the upstream phone product entirely, so it cannot appear in an estimate.

That sixth one is worth calling out because the first five all act on the field a caller asked for, and none of them looks at what we store. The raw provider object is copied verbatim from upstream, persisted with the result and published on the public API — so a provider that started attaching a mobile number to a person entity would have walked past all five. Scrubbing it on the way in closes that, and it is a key-name filter rather than a guarantee that no digit anywhere can be a phone number.

One boundary, stated so it is not mistaken for more than it is: none of this constrains what you send us. A phone column inside a CSV you upload is stored with the rest of that file. We never read it and never act on it, but it is in the database until the upload is deleted — see Section 5 of our Privacy Policy.

Removing a capability is the only security control that cannot be misconfigured. This is the clearest example of one we have.

7. Webhooks

If you configure a webhook URL for a job, the completion callback is signed with HMAC-SHA256 using a secret derived per organization rather than stored, and carries a timestamp with a bounded replay window. Verify the signature and reject stale timestamps at your endpoint — that is what lets you trust the payload came from us and is not a replay.

8. Handling of upstream credentials and errors

  • Provider secrets are read from environment variables at the point of use and are never included in anything sent to the browser.
  • The origin used for public canonical URLs is kept separate from the origin used for authentication callbacks, so a change to marketing URLs cannot widen the auth surface.
  • Authentication callbacks are restricted to an explicit allowlist of trusted origins: localhost for development, enrichments.io and its subdomains in production.
  • Errors from our email-verification path never carry the upstream endpoint or the upstream response body. Those are stripped before an error can reach a log line or a support thread, and callers receive a mapped result rather than a pass-through payload.
  • Uploaded CSVs are hard-capped at 4 MB with row and column ceilings, chosen below the platform's request-body limit so an oversized upload fails cleanly rather than at the edge.

9. Result provenance

Every enriched field is stored with evidence: which provider and endpoint produced the value, and how confident it is. This is provenance for results, so you can audit where a record came from and judge whether to trust it. It is not access logging, and we are not presenting it as such — see Section 11.

10. Data in transit, at rest, and retention

Traffic to the application and to our upstream providers travels over HTTPS. We are not quoting a minimum TLS version or cipher suite here, because those are properties of our hosting platform rather than settings we configure and could verify for you.

Data at rest lives with a managed database host. Whatever encryption-at-rest that host provides applies; we do not add an application-level encryption layer on top, and we are not going to describe one we have not built.

Retention is covered honestly and in detail in Section 9 of the Privacy Policy. The short version: cached upstream responses are used for a bounded period but are not deleted on a schedule today, most other records have no automatic expiry, and deletion is a manual process we carry out on request.

11. What we do not claim

Listing absences explicitly is more useful to a security reviewer than a page of implied controls. As of the date at the top of this page, Enrichments has:

  • No SOC 2 report, no ISO 27001 certification, and no other third-party security certification — held or in progress.
  • No third-party penetration test or external security audit.
  • No uptime commitment, service level agreement or availability monitoring commitment.
  • No audit log of administrative or support access. A table for one exists in our schema and nothing writes to it; we would rather tell you that than let its existence imply a trail.
  • No multi-factor authentication, and no enterprise single sign-on beyond the optional third-party identity provider described in Section 2.
  • No application-level encryption of stored credentials or tokens beyond password hashing.
  • No automated data-retention or purge schedule. The purge routine exists but is not scheduled.
  • No stated internal policies about production access, offboarding, dependency patching or abuse monitoring. Whatever practices the operator follows are not documented here, and we are not going to assert controls we cannot evidence.
  • No payment processing. Card details are never collected, transmitted or stored by us or on our behalf, so questions about PCI scope do not arise.
  • No error-reporting vendor in the stack, and no session recorder. There is one email-delivery vendor, used only for the four account and security messages listed in Section 3 of the Privacy Policy; it never receives enrichment results, uploaded rows, chat messages or credentials. Web analytics and advertising conversion measurement do now run, but only on the public marketing pages and only with the visitor's consent — never on any page inside the product, so no application URL, query, upload or result reaches them. Section 7 and Section 10 of the Privacy Policy describe them by category and set out exactly what they receive.

If any of these becomes a requirement for your organization, tell us before you rely on the Service rather than after.

12. Reporting a vulnerability

If you believe you have found a security issue, please report it privately to security@enrichments.io before disclosing it publicly, with enough detail to reproduce it. We will acknowledge your report and keep you updated while we investigate. We ask that you do not access, modify or delete data belonging to anyone else, do not degrade the service for other users, and give us reasonable time to fix the issue. We will not pursue legal action against researchers who follow those principles in good faith. We do not currently run a paid bounty programme.

13. Changes

This page describes a product that is actively being built, and it will change as the product does — including, we hope, by moving items out of Section 11. The date at the top reflects the last revision. Questions from procurement or security teams are welcome at security@enrichments.io.