Gate
MainnetGate answers one question over HTTP: does this subject hold a verified on-chain attestation that satisfies my policy? Point it at an @handle or a wallet, hand it a policy, and get back a decision with the attestations behind it. You reuse X1ID's verification oracle as your allowlist instead of running your own.
This is the hosted version of the SDK's gating primitive. If you'd rather read chain yourself (or gate on-chain from another program via a CPI), see Attestation gating — same policy shapes, same staleness rules, no key needed.
Check a subject
Body: exactly one of handle / wallet, plus a policy. A wallet subject is gated on its primary @handle.
curl -X POST https://api.x1id.io/v1/gate/check \
-H "X-API-Key: $X1ID_API_KEY" -H "content-type: application/json" \
-d '{ "handle": "@jack", "policy": { "kind": 4 } }'{
"passed": true,
"subject": { "handle": "jack", "wallet": null },
"policy": { "kind": 4 },
"attestations": [
{ "platform": "github", "kind": 4, "attested_at": 1733270400 }
]
}| field | meaning |
|---|---|
passed | the decision. |
subject | who was evaluated. For a wallet subject, handle is its resolved primary (or null). |
policy | the policy that ran. |
attestations | the live attestations that count — the which behind the decision. |
reason | present only when passed is false for a structural reason: no-primary-handle (the wallet has no primary) or handle-not-registered. Lets you tell "no identity" from "identity, but doesn't match." |
A definitive "does not pass" — an unregistered handle, a wallet with no primary, or no matching attestation — is a real 200 { "passed": false } decision (and is metered). Only malformed input (400) and infrastructure failure (502) are non-decisions.
Policies
A policy is what the subject must prove. Three inline shapes, mirroring the SDK's Gate type exactly:
| Policy | Passes when the handle holds… |
|---|---|
"any-verified" | at least one non-stale attestation of any kind. |
{ "kind": 4 } | a non-stale attestation of exactly that kind. |
{ "anyOf": [2, 4] } | a non-stale attestation of any listed kind (X or GitHub). |
Attestation kinds: 0 dns · 1 social (legacy) · 2 x · 3 discord · 4 github · 5 telegram. A policy with an out-of-range kind is rejected at definition time (400 BAD_GATE), never silently never-matching.
Named policies
Define a policy once and reference it by id — handy when the same door is checked from many places. Policy management is keyed only (a policy is scoped to your API key).
curl -X POST https://api.x1id.io/v1/gate/policies \
-H "X-API-Key: $X1ID_API_KEY" -H "content-type: application/json" \
-d '{ "name": "github-verified", "gate": { "kind": 4 } }'
# → 201 { "id": "gp_…", "name": "github-verified", "gate": { "kind": 4 }, "created_at": "…" }Then pass the gp_… id as the policy in a check:
curl -X POST https://api.x1id.io/v1/gate/check \
-H "X-API-Key: $X1ID_API_KEY" -H "content-type: application/json" \
-d '{ "wallet": "H8Fs…X1HiA", "policy": "gp_…" }'GET /v1/gate/policies/:id reads one back (owner-scoped). A gp_… string is always treated as a stored-policy reference; any other value is an inline policy.
Metering
A gate decision is metered exactly like a resolve: with a key it counts against your rate limit; with no key, each decision can be paid for per call via x402. Defining and reading named policies always requires a key.
What a pass means — and what it doesn't
Trust in the gate = trust in the attestor, exactly like the blue-tick.
passed: truemeans the handle carries a live, attestor-signed attestation matching your policy. The attestor is X1ID's verification oracle.- It is "verified by x1id", not trustless proof and not legal personhood. The attestor key is admin-rotatable. Render the result as exactly that.
- Staleness is enforced for you. An attestation only counts for the current owner — one left from a previous owner (before a transfer, sale, or re-register) never counts.
- Ownership changes, so re-check per session. Pair a gate check with Connect to first prove the visitor controls the handle, then gate on what it's verified.
