Verification requests
A verification request is the reusable definition of what you are asking for: which credential shape, which conditions on its values, and — where it matters — which issuers you will accept. You define it once and run it against as many people as you like.
Verification requests are created in the dashboard under Verifications. The create and update endpoints require a dashboard session, so they are not reachable with an API key today; see Authentication.
Schemas and attributes
A schema defines the shape of a credential — its attributes and their types. Two default schemas ship with every organization (Work Certificate and Educational Certificate), the identity schemas are issued through Socious Verify’s KYC flow, and you can publish your own under Schemas.
Attribute types are TEXT, NUMBER, BOOLEAN, URL, DATETIME and EMAIL.
Single-use and reusable
type | Behaviour |
|---|---|
MULTI | Reusable. Many people complete the same verification. Use for a general requirement — an age gate, a membership check. |
SINGLE | One person. Use when you are verifying a named individual for one occasion. |
Conditions
A verification carries zero or more conditions, each one an attribute, an operator and a value:
| Operator | Meaning |
|---|---|
EQUAL | Attribute equals the value |
NOT | Attribute does not equal the value |
BIGGER | Attribute is greater than the value |
SMALLER | Attribute is less than the value |
All conditions must pass. One failure makes the whole individual FAILED, and
validation_error names the check that failed.
Dates are compared as dates, which is what makes age checks work: to require someone is
over 18, put a SMALLER condition on date of birth against the date 18 years ago —
“born before” is how “older than” is expressed.
The shape of a stored verification, as returned by the API:
{
"id": "339ac66a-...",
"name": "Voting eligibility",
"description": "Members eligible to vote in the governance round",
"schema_id": "16ffcc82-...",
"type": "MULTI",
"trusted_issuers": [],
"attributes": [
{ "attribute_id": "6f0a...", "operator": "EQUAL", "value": "true" }
],
"created_at": "2026-08-05T09:02:11Z"
}
Trusted issuers
A signature check proves a credential was not tampered with. It does not prove the signer is anyone you trust — and for most real decisions, who issued the credential is the whole question. A credential someone issued to themselves is perfectly valid cryptographically.
Each verification therefore carries a trusted_issuers list of issuer DIDs:
- Empty list — any issuer is accepted. This is the default and the historical behaviour, so every verification created before this existed keeps working unchanged.
- Non-empty list — only those DIDs are accepted. The issuer is checked before the conditions, so a credential from an untrusted issuer is never rescued by carrying the right values. The refusal names the issuer it rejected.
- A credential whose issuer cannot be identified is refused whenever the list is non-empty. Unknown is never treated as a wildcard.
Every settled verification also records what it saw, in issuer_did on the individual —
whether or not that verification pins anything. That gives you the evidence to answer
“what do our people actually present?” before you decide what to accept.
Setting the list — send trusted_issuers on create or update:
curl -X POST https://api.shinid.com/verifications \
-H "Authorization: Bearer SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Voting eligibility",
"description": "Members eligible to vote",
"schema_id": "SCHEMA_ID",
"type": "MULTI",
"trusted_issuers": ["did:prism:your-issuer"]
}'
On update the field has three states, and the difference matters:
| You send | Result |
|---|---|
| the field omitted | the stored list is kept — an ordinary rename cannot silently unpin |
"trusted_issuers": [] | pinning stops; any issuer is accepted again |
"trusted_issuers": [dids] | replaces the list |
The dashboard has no field for it yet, so a verification edited there keeps whatever you
set through the API. You can rehearse the whole thing before it matters — a
sandbox verification accepts trusted_issuers and
simulate takes an issuer_did, so you can watch a credential from the wrong issuer be
refused without needing one.
Individuals
Every run of a verification against one person is a verification individual, keyed by
your customer_id. Listing them:
# Session token — the dashboard's own list
GET /verifications/{verification_id}/individuals?page=1&limit=50
# API key — one person, by your own identifier
GET /verifications/{verification_id}/individuals/{customer_id}
List endpoints accept page (default 1) and limit (default 10, maximum 100), and return
{ "results": [...], "total": n }.