Draft - for the owner to correct. Highlighted notes mark facts about the company that only the owner can settle, and mailboxes that have not been set yet. Each one is answered in one place - src/content/company.ts for a fact, the site environment for an address - and this banner goes when the last of them is answered.
Security and trust

What is actually implemented, and what is not.

This page answers the questions a security review sends us, and it is written from the code rather than from a template. Where a control exists it is described by what it does; where one does not exist it is missing from this page, and where the honest answer is uncomfortable it is on the page anyway. Parameters are deliberately absent: how many attempts, over how long, and how anything is constructed are not published, because publishing them would be publishing the way round them.

Accounts and sign-in

A session is a record we can end, not a token you carry.

Sessions are server-side and revocable

Every session is a row in our database, and the cookie holds a random token of which only a hash is stored - a stolen database does not yield usable sessions. The cookie is HTTP-only, same-site, and marked secure outside local development. A session expires both on idle and at an absolute age, checked every time it is used, and a session belonging to a disabled user stops working immediately.

Signing out everywhere is one action

A user can end every other session on their account from the application. It also happens on its own where it matters: changing a password ends the other sessions, turning on two-step sign-in ends them, and a password reset ends all of them including the one that asked. An administrator can end a colleague's sessions, and that is written to the audit log.

Passwords are hashed with Argon2id

Stored as an Argon2id hash and re-hashed transparently when the parameters move on. Sign-in does the same work whether or not the address exists, so the form cannot be used to find out who has an account. Every state-changing request also carries a CSRF token, compared in constant time.

Two-step sign-in, honestly described

Available to every user with an authenticator app. The shared secret is encrypted at rest, a code is accepted once and a replayed code is refused, comparisons are constant-time, and recovery codes are single-use and stored only as hashes. It is a per-user setting: we cannot require it across an organisation today, and no single action in the product demands it. Where an account has it on, single sign-on is refused for that account rather than bypassing the code.

API keys

A key reaches one organisation, through the scopes you gave it.

A key is shown once, at creation, and only its hash is stored - there is no way to read one back, including for us. It carries an organisation and no user identity, so anything that needs a person is closed to it by design rather than by a list of exceptions: passwords, invitations, billing, and creating or revoking keys cannot be done with a key at all. A key can never be given a scope the person who created it did not hold, and asking for one is refused rather than quietly narrowed.

ScopeWhat it grants
providers.readSearch and read providers, organisations and facilities.
providers.exportStart an export and download the file it produces.
contacts.revealOpen the contact details of one provider at a time, metered and logged.
append.runSubmit a file to the append tool and collect the result.
jobs.readRead the status and result of jobs this key started.
usage.readRead the organisation plan and its month-to-date usage.

Pace and quota, reported rather than hidden

Calls are paced per minute and counted against a monthly quota, and both are in the response headers of every call so your own code can see where it stands. A call refused for pace or quota is not charged against the monthly quota. It does count against the per-minute window, so ignoring a refusal does not earn a fresh allowance. The per-minute count is shared across our servers rather than held in one process, so the limit is the limit.

Revoking, expiring, and what rotation means here

A key can carry an expiry date and can be revoked at any time; the row is never deleted, so who revoked what and when has an answer. Each key records when and from where it was last used, so “is anything still calling with this” can be answered before you revoke it. There is no rotate-in-place operation: rotation is creating the new key, moving your callers, then revoking the old one. Every refused call answers the same way whatever was wrong with the key, so a probe learns nothing from the difference.

Scopes for other data domains exist in the platform for our own operations and are outside what any customer account can hold, so a customer key cannot be given one. The API reference is at /docs/api.

Auditing and exports

Every reveal, export and append is recorded against the organisation.

What the log holds

Contact reveals, exports and their downloads, append runs, sign-ins and failed sign-ins, single sign-on, two-step changes, key creation and revocation, user invitations and changes, suppression and removal-request decisions, and billing changes. Each entry names the organisation it belongs to. The action must be one the code knows, and an entry carrying anything that looks like a password or a token is refused rather than written - a log is not a second place secrets live.

What the log is not, yet

It is internal. There is no endpoint that hands a customer their own audit log today, so treat it as evidence we can produce rather than a report you can run. Its retention is being settled with counsel and will be stated in the Privacy Policy rather than guessed at here.

An export is traceable to the export

Each row of a provider export carries a reference that resolves to the export that produced it, and the file name carries the same reference so a file is recognisable even with the column removed. The file is hashed and recorded against the organisation, the user, the job and the data loads it read, and a file of ours coming back to us - in an append, for instance - is recognised as one of ours.

Provider exports are fingerprinted to you

A provider export carries a small number of synthetic records unique to the organisation that exported it. They are not real people and cannot collide with a real provider or a real phone line, they are not charged against your allowance, and every placement is recorded internally. If a file turns up where it should not be, they say whose export it was. How they are made and where they sit is not published, for the obvious reason.

Anti-scraping and abuse

The controls that make bulk extraction fail rather than succeed slowly.

Described by effect only. The thresholds, the windows and the order things happen in are not published anywhere on this site.

Repeated failures stop being answered

Enough failed sign-ins and the attempts stop being answered. Wrong two-step codes stop being answered for that account however many places they arrive from, so spreading the attempt around does not walk past the control. Sign-ups, password resets, single sign-on callbacks and public removal requests are paced too, and the sign-up and password-reset forms can require a human check. These are pauses that lift themselves rather than permanent locks.

Contact reveals are paced, and can be suspended

New reveals are paced per organisation and per actor, whether the actor is a person or a key, so one API key cannot outrun what a browser could do. An organisation that keeps hitting that pace, or opens an unusual volume of new contacts in a day, has new reveals suspended: records already opened still read back, nothing new opens, and only we can clear it - after looking at what happened. The lock, its reason and the clearing are all in the audit log.

Contact details are never in bulk anywhere

No public page carries a work email, a direct phone, a home address or a cell phone, and no page names a person who does not hold an NPI - leadership is counted by role in public and named only for signed-in customers. A contact is opened one record at a time, against an allowance, and written to the log. There is no endpoint, and no plan, that hands over a file of contact details.

The public surface is deliberately small

On the public API host, only the versioned API, its schema, a health check and one signature-checked billing webhook are served. Everything else answers 404 rather than redirecting or proxying, so the administrative side of the platform is not reachable by guessing a URL. The internal application is not published to the internet at all. Client addresses are read only from a proxy we trust, so a forwarded header cannot be forged to defeat the pacing above.

Suppression

One definition of suppressed, used by everything that reads the data.

One rule, two languages, no drift

Suppression is defined once and compiled into both the database queries and the application code, so search, a record page, the published counts, exports, append and the API cannot disagree about whether something is suppressed. Values are normalised on both sides, so a phone number written one way on a request matches the same number written another way on a record.

Global, and at the grain the person asked for

A suppression can cover a value wherever it appears, one field of one subject, or a subject entirely. It applies to every customer, because an opt-out one team honours and another does not is not an opt-out, and it takes effect on the next request rather than when a cache expires. Customers cannot add or lift one.

Never a deletion

Records are rebuilt from filings regularly, so a removal has to survive the rebuild: the mark is held against the identifier rather than the row. Lifting a suppression keeps the record of it too, so “why did this start appearing again” has an answer. What each kind of request does is set out in your privacy choices, and the form is remove my information.

A public form that does not write to the database

The removal form is open and anonymous, so it confirms the request by email and a person reviews it before anything is suppressed. A form that acted on submission would let anyone suppress somebody else's listing.

What is held

The data that is not here is the answer to half the review.

No patient or clinical data

There is no patient-level record in the platform in any form - not identified, not de-identified, not aggregated - and no clinical note, diagnosis or outcome. Published Medicare utilisation statistics are held as provider-level figures exactly as the federal publisher released them, including that publisher's suppression of small counts, which is preserved rather than filled in.

Home address and cell phone are gated three ways

Provider-matched home address and cell phone require a platform setting, a plan that carries the contract flag, and a per-reveal charge against the allowance - all three, not any one. A payment event can never move an organisation onto a contract-only plan, so it cannot be bought online. Only a confirmed match is ever sellable: a probable one reads as nothing on file, so you are not charged for a guess. Where the gate refuses, it says which gate refused.

Other data domains are not reachable from a customer account

The platform serves our own operations as well as this product, and the permissions for those other domains sit outside what any customer account can hold - not behind a plan, outside the ceiling entirely. A customer account cannot be configured into them, and no customer API key can carry their scopes.

No advertising or analytics scripts

This site loads no advertising, analytics or session-recording script. That is not a promise in a policy: the names of the common ones are in a check that runs on every lint, so a pull request that added one would fail. The one third party a form loads is the human check, and the Privacy Policy names it.

Transport and secrets

What the edge enforces, and what is encrypted.

At the edge

TLS with automatically renewed certificates, HTTP Strict Transport Security including subdomains, and a content security policy that permits no inline script - every page is rendered on the server, so none is needed. Framing is denied, content-type sniffing is off, the referrer policy is strict on cross-origin, camera, microphone and location are denied, and the server banner is stripped. Form submissions are restricted to us and the payment provider.

How secrets are stored

Passwords and API key secrets are Argon2 hashes. Session, CSRF, invitation, password reset, email verification and recovery-code tokens are stored only as hashes. Two-step secrets and organisation settings are held with authenticated encryption. Backups are encrypted before they leave the server. We are not going to claim database or full-disk encryption at rest: what the hosting layer does underneath is not something we can evidence to you, so we do not put it on this page.

Reporting a vulnerability

Tell us, and we will confirm we have it.

Send what you found and enough to reproduce it. In scope: this site, the customer API, and the application behind sign-in. Please do not test against other people's accounts or records, do not run load or denial-of-service tests, and take no more data than the minimum that demonstrates the problem - if a single record proves it, one record is the right amount. Tell us before you tell anybody else, and we will tell you when it is fixed.

Contact routes

[OWNER: the address for security reports, set as SECURITY_EMAIL in the site environment. Until it is, this route is a page rather than a mailbox.]

Assurance, and whether a report earns anything

[OWNER: whether there is an audit report, a certification or a third-party penetration test a buyer's security review may be pointed at - until you say there is, this page claims none]

[OWNER: whether a security report can earn a reward, and on what terms - until you say so, this page says there is no programme to point a researcher at]

If the problem is that your own details are in the database, that is not a security report and you do not need to write to anybody: use remove my information. If a login or an API key has been compromised, tell support and revoke the key - the contact page has the route.

Questions

What security reviews ask us.

Is two-step sign-in available, and can we require it?

It is available to every user, using an authenticator app, with single-use recovery codes. It cannot be required across an organisation today: it is a per-user setting, and there is no org-wide switch. We would rather say that than imply a control we do not have. Where an account has two-step sign-in on, single sign-on is refused for that account rather than quietly bypassing the second factor.

What can somebody do with a stolen API key?

Only what the key's scopes allow, only against the organisation it belongs to, and only within its pace and monthly quota. A key carries no user identity, so anything that needs a person - changing a password, inviting a user, creating or revoking keys, reading billing - cannot be done with one at all. Keys are shown once and stored only as a hash, so nobody, including us, can read one back. Revoke it and the row stays, with who revoked it and when.

Can we get an audit trail of what our team revealed and exported?

Every reveal, export, append, sign-in, key creation and key revocation is written to an audit log against your organisation, and it is how we trace misuse of the data. Be clear about the limit, though: there is no endpoint that returns the log to a customer today, so it is evidence we hold rather than a report you can run.

How do you know an export file came from us?

Every row of a provider export carries a reference that resolves to the export that produced it, the file name carries the same reference, and the file itself is hashed and recorded against your organisation, the user who ran it and the data it read. Provider export files also carry a small number of synthetic records unique to the organisation that exported them - they are not real people, and if a file appears somewhere it should not, they say which export it came from.

Is there any patient data in the platform?

No: no patient-level record in any form, identified, de-identified or aggregated, and no clinical note, diagnosis or outcome. Where the platform holds published Medicare utilisation statistics they are provider-level figures as the federal publisher released them, including that publisher's own suppression of small counts, which we preserve rather than filling in with a zero.

What happens to data about a person who asks to be removed?

A suppression, applied everywhere - search, pages, counts, exports, append and the API - and it holds through the weekly refresh. The record is not deleted, because a deleted record is one the next refresh recreates. Suppression is global rather than per-customer: an opt-out one team honours and another does not is not an opt-out. Customers cannot add or lift a suppression.

The rest of the review is the data itself.

What a record holds, how current each part of it is and what the platform does not hold are on the data page. What a customer may and may not do with it is in the terms.