Check a sign-up
Check users against the Shared Registry, read the result, and act on a match
A check asks the registry whether any member reported a user. Send it when a user signs up, before the account is active. You can also check at login and in a periodic re-check of existing users.
Send a check#
POST /api/v1/registry/check needs a key with the registry:check scope.
| Field | Type | Description |
|---|---|---|
identifiers |
array | One to five identifiers for one user. Each is { "type": "email", "sha256": "<digest>" }. |
context |
string | Why you are checking: signup, login or periodic. |
sha256 is the lowercase hexadecimal digest from Hash an email. Send more than
one identifier when the user gave you more than one email, for example a login email and a recovery
email. The registry ignores repeated identifiers.
curl -X POST https://api.omnifence.ai/api/v1/registry/check \
-H "Authorization: Bearer $OMNIFENCE_REGISTRY_KEY" \
-H "Content-Type: application/json" \
-d '{
"identifiers": [
{ "type": "email", "sha256": "d6117306485ed0e50afab3ac871e98f81699151f30281527d63ff5f233656c69" },
{ "type": "email", "sha256": "5a0b70300b36ef454b660f484ad2a9353e2b4e199f623fe55789406eb07055f5" }
],
"context": "signup"
}'
Send identifiers for one user per request. Do not combine several users in one check: the result cannot tell you which of them matched.
Read the result#
{
"match": true,
"signals": [
{
"category": "payment_fraud",
"reporter_count": 1,
"first_reported_at": "2026-05-11T14:02:00.000Z",
"last_reported_at": "2026-05-11T14:02:00.000Z",
"automated_refusal_permitted": false
},
{
"category": "prohibited_content",
"reporter_count": 2,
"first_reported_at": "2026-03-02T10:15:00.000Z",
"last_reported_at": "2026-08-19T08:40:00.000Z",
"automated_refusal_permitted": true
}
],
"normalisation_version": 1
}
| Field | Description |
|---|---|
match |
true when at least one live entry matches any of the identifiers. |
signals |
One object per matching category, sorted by category. Empty when match is false. |
normalisation_version |
The version of the hashing rules the registry expects. Currently 1. |
Each signal combines every member's report in that category. See What a check returns for the fields. Your own reports count too: if you reported the user, your check matches your report.
Act on a match#
A match is information for your own decision. Use the signals to choose an action:
| Signals | Recommended action |
|---|---|
| None | Continue the sign-up. |
At least one with automated_refusal_permitted: true |
You can refuse the sign-up. |
Only signals with automated_refusal_permitted: false |
Hold the account for review by your team, or apply extra verification. Refuse only after a person reviews it, or with a clear way to contest. |
You can use reporter_count and the dates to weigh a signal. For example, a recent report from
several members is stronger than one old report.
This example implements the check and the decision for a sign-up handler:
import { registryDigest } from './registry-hash.mjs';
const REGISTRY_CHECK_URL = 'https://api.omnifence.ai/api/v1/registry/check';
export async function checkRegistry(emails, context) {
const identifiers = [...new Set(emails.filter(Boolean).map(registryDigest).filter(Boolean))]
.slice(0, 5)
.map((sha256) => ({ type: 'email', sha256 }));
if (identifiers.length === 0) return { match: false, signals: [] };
const response = await fetch(REGISTRY_CHECK_URL, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.OMNIFENCE_REGISTRY_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ identifiers, context }),
signal: AbortSignal.timeout(3000),
});
if (!response.ok) {
const body = await response.json().catch(() => ({}));
throw new Error(`Registry check failed: ${response.status} ${body.error ?? ''}`.trim());
}
return response.json();
}
export function registryDecision(result) {
if (!result.match) return 'allow';
if (result.signals.some((signal) => signal.automated_refusal_permitted)) return 'refuse';
return 'review';
}
// In your sign-up handler
let decision = 'allow';
try {
decision = registryDecision(await checkRegistry([email, recoveryEmail], 'signup'));
} catch (err) {
// Do not block sign-ups when the registry cannot answer. Record the user
// and check again later with context "periodic".
console.warn(err);
}
What to tell the user#
- Do not tell the user that they are in the registry, which category matched, or how many platforms reported them. Use your normal message for a refused or held account.
- If you refuse or hold an account, give the user your usual route to contest the decision. If the user wants to know what the registry holds about them, refer them to Disputes.
- You do not know which member reported the user, so you cannot pass that on.
When the registry cannot answer#
The registry is one signal among your own controls. If a check fails, by a timeout, a 5xx or a
429, continue the sign-up as if there was no match, and check the user again later with context
periodic. Do not retry a failed check in a tight loop: it counts against your quota and your rate
limit. See Errors.
Check at login and periodically#
login: Check when an existing user logs in, if you want to find users that another member reported after they signed up with you.periodic: Re-check existing users on a schedule, for example accounts that signed up while the registry was unreachable.
The check request and the result are the same in every context. Choose the context that describes why you check: Omnifence monitors the mix of contexts for each member.
Daily check quota#
Your membership has a daily check quota, sized to your sign-up volume. Each check request counts once, whatever number of identifiers it carries. The count resets at 00:00 UTC.
A check over the quota returns 429 RATE_LIMITED with the message
Daily Shared Registry check quota of <quota> reached, and does no lookup. If your volume grows,
contact hello@omnifence.ai to raise the quota.
Registry keys also follow your account's rate limits: by default, 60
requests per minute and 6 per second. A 429 from the rate limit carries a retry-after header; the
quota 429 does not.