# The Collect SDK Source: https://docs.solo.one/api-overview/collect-sdk/overview A hosted, drop-in flow for collecting first-party identity data and consent > [!NOTE] > > **Available to registered partners only.** The Collect SDK is provisioned per > partner. Contact your SOLO representative to request access and a sandbox. ## What it is The **Collect SDK** is a ready-made screen flow that asks a person for their information and permission, so you don't have to build those screens yourself. You hand the consumer off to it, they answer a few guided steps — who they are, a quick phone check, what they agree to share — and the result comes back to you through the SOLO network. Think of it as the "front door" where data and [consent](/concepts/identity/consent) are gathered directly from the consumer. It's the human-facing counterpart to SOLO's machine interfaces: the [Furnishing API](/api-overview/furnishing/overview) (how data gets *into* a network) and the [Query API](/api-overview/querying/overview) (how data is read back). ## Why it's useful * **You skip building intake UI.** Identity capture, phone one-time-passcode verification, consent screens, dynamic forms, and conflict resolution are all provided and kept current by SOLO. * **Consent is captured at the source.** The consumer's [consent](/concepts/identity/consent) is recorded in the same flow that collects their data, so the legal basis travels with the data from the moment it's gathered. * **You can change what's collected without a release.** The steps and fields are decided on the server, so product and compliance changes don't require shipping new front-end code. ## When and why you'd use it Reach for the Collect SDK whenever you need a real person to provide information or grant permission as part of a flow you run: | Situation | How the Collect SDK helps | | - | - | | **Onboarding a new customer** | Walks them through identity, phone verification, and consent in one guided flow, then makes the result available to the network. | | **Getting permission to query someone** | Captures the consumer's [consent](/concepts/identity/consent) so you can later run a [product query](/api-overview/querying/overview) about them. | | **Collecting missing details** | Presents a dynamic form for exactly the fields you still need — without you designing the form. | | **You don't want to build or maintain intake screens** | SOLO hosts and updates the experience; you just launch it. | If your data already lives in your own systems and no consumer interaction is needed, you'll typically use the [Furnishing API](/api-overview/furnishing/overview) instead. The Collect SDK is specifically for the moments a consumer is in the loop. ## How it works > [!NOTE] > > The Collect SDK is a **hosted experience**, not a code library you bundle. You > integrate by minting a short-lived session token from your backend and opening > the hosted SDK URL — there's no client package to install or upgrade. ```mermaid theme={null} flowchart LR App[Your application] -->|mint session token| SDK[Collect SDK
hosted flow] SDK -->|guided steps| User((Consumer)) User --> SDK SDK -->|consent + collected data| Net[(SOLO network)] ``` **1. Mint a session token** Your backend requests a short-lived SDK session token, scoped to the workflow you want the consumer to complete (and, optionally, the [entity](/concepts/identity/entities) it concerns). Tokens are single-purpose and time-limited. **2. Launch the hosted flow** Send the consumer to the hosted Collect SDK URL with the token. The SDK validates the token and renders the first step. No token, no flow — the SDK will not start without a valid session. **3. The consumer completes the steps** The SDK drives a step-by-step flow (below), advancing as each step is submitted. The sequence and the fields requested are decided server-side by the workflow. **4. Hand back to your app** On completion the consumer returns to your application, and the consented, collected data is available in the network for furnishing or querying. ### The collection flow A Collect session is a sequence of server-driven steps. A given workflow uses the subset it needs: | Step | What the consumer does | | - | - | | **Identity** | Provides core identifying details (e.g. name and phone number). | | **Phone verification** | Confirms ownership of their phone with a one-time passcode. | | **Consent** | Affirms or denies consent per attribute, for exactly the attributes the workflow requests. See [Consent](/concepts/identity/consent). | | **Collect** | Fills in a dynamic form — fields (email, address, dates, selections, and so on) are specified by the workflow, not hard-coded in the UI. | | **Conflict resolution** | Chooses between conflicting values when the same attribute arrives from more than one source. | | **Review** | Confirms the aggregated consents and collected values before submitting. | Because the steps and fields are defined by the workflow on the server, the same hosted SDK can power very different collection experiences — a light phone-and- consent capture, or a full onboarding intake — without any change on your side. ## Related concepts > **[Consent](/concepts/identity/consent)** > > The permission the SDK captures alongside the data. > **[Entities](/concepts/identity/entities)** > > The consumer or business a Collect session concerns. > **[Furnishing API](/api-overview/furnishing/overview)** > > How collected data is contributed into a network. > **[Query API](/api-overview/querying/overview)** > > How collected, furnished data is read back. # JSONL Files Source: https://docs.solo.one/api-overview/furnishing/file-upload Furnish batches of records as JSONL files, from the dashboard or over SFTP A JSONL file furnishes a whole batch of records at once. There is one file format and two ways to deliver it: * **The dashboard** — upload a file from the SOLO dashboard. No credentials or file-transfer infrastructure beyond your sign-in. * **[SFTP](/api-overview/sftp/overview)** — drop files from systems that already produce them on a schedule. Both land in the same place and run through the same [furnishing pipeline](/api-overview/furnishing/overview) as a per-record REST furnish. A record uploaded in a file and the same record furnished over HTTPS produce identical network data. ## File format A JSONL file is UTF-8 text with **one JSON object per line**: ```json furnish_records.jsonl theme={null} {"_type":"kyc_certificate","ssn":"123456789","dob":"1990-01-01","first_name":"Alice","last_name":"Anderson"} {"_type":"kyb_certificate","ein":987654321,"jurisdiction":"DE","legal_name":"Acme Holdings LLC"} ``` * **Extension** — `.jsonl`. * **Filename** — exactly one dot, and a name made of letters, digits, underscores, and hyphens: `consumers_aug.jsonl` is valid; `consumers aug.jsonl` (space) and `consumers.aug.jsonl` (two dots) are rejected. * **Blank lines** are skipped and don't count as records. * **Every line is independent.** A line that isn't valid JSON fails on its own; the rest of the file still ingests. * **Dashboard uploads** are limited to 10 MB per file. ## Record types Every record carries a `_type` field naming what kind of record it is. That value is how the network decides which [mappings](/concepts/governance/furnishing-policies) receive the record: * Each **feed** in the dashboard declares a sample record. The sample's `_type` is the record type that feed's mappings claim. * A record is sent to **every active mapping** in the target network that claims its `_type`. One record can therefore feed several mappings — for example, one that persists document-capture events and another that persists biometric-capture events from the same line. * A record whose `_type` **no mapping claims** is counted as *unmatched* and skipped. Unmatched records never fail the file: they're expected when a file carries record kinds this network doesn't ingest. * A mapping can also bound when it accepts records. A record whose type is claimed but that arrives outside every claiming mapping's window is reported separately from an unclaimed type, so the two causes are easy to tell apart. The rest of each record is whatever your mapping reads. The mapping's source schema, shown on the feed in the dashboard, lists the fields it requires. ## Choosing the network Records are written to exactly one network per file: * **Dashboard** — the upload dialog asks which network to furnish into. It selects the network automatically when you can furnish into only one. * **SFTP** — put the file under `network_/jsonl_furnish_data/` to target a network explicitly. A file directly under `jsonl_furnish_data/` goes to your organization's governed network. Either way, your organization must hold the **furnisher** or **governor** [role](/concepts/governance/network-roles) in that network. ## Uploading from the dashboard The same upload dialog opens from three places: * **Upload JSONL records** in the command palette. * **Furnish** on a product's detail page. * **Upload data** on an SFTP feed's detail page. Pick the file, confirm the network, and submit. The upload is accepted immediately and ingested in the background. ## Results When ingestion finishes, the upload records what happened to every line: | Count | Meaning | | - | - | | **Records** | Non-blank lines read from the file. | | **Succeeded** | Record and mapping pairs that persisted. | | **Filtered** | Record and mapping pairs a mapping skipped on purpose (for example, a filter excluded them). Filtered is not failed. | | **Failed** | Lines that weren't valid JSON, plus record and mapping pairs that errored. | | **Unmatched** | Records whose `_type` no active mapping claims. | Errors are grouped by cause, with sample line numbers, so a thousand records failing the same way show up as one error rather than a thousand. Review them on the dashboard's **Uploads** page, or on a feed's recent deliveries, which also name the unmatched record types. > [!TIP] > > Re-sending a corrected file is safe. Identity records resolve to the existing > consumer or business on their natural keys (SSN and date of birth for > consumers; tax identifier and jurisdiction for businesses), so a re-upload > attaches to the same profile instead of creating a duplicate. ## Failure modes > **The file is rejected before upload** > > The dashboard checks the extension, the filename rules, and the size before > sending anything. Rename the file (letters, digits, `_`, `-`, one dot, > `.jsonl`) or split it into smaller files. > **Every line fails as invalid JSON** > > The file isn't UTF-8 JSONL — often a JSON array (`[{…},{…}]`) or a > pretty-printed object spread across several lines. Write one compact object > per line. > **Records are counted as unmatched** > > No active mapping in the target network claims their `_type`. Check the > value against the feed's sample record, confirm the mapping is active > rather than a draft, and confirm the file went to the network you expected. > **Records fail against a mapping** > > The record doesn't carry what the mapping reads — usually a missing required > field or a value of the wrong type. The grouped error names the field; > compare the record with the mapping's source schema. ## In the dashboard ![Product furnish — JSONL records ready to upload](https://mintcdn.com/soloone/9HdwBb7FHoPnTmO1/images/dashboard/journeys/furnishing/furnishing-complete/05-product-furnish-upload-filled.png?fit=max\&auto=format\&n=9HdwBb7FHoPnTmO1\&q=85\&s=a644c791630bc349fc2ae319c5d28dd5) ![Product furnish — upload accepted, dialog closed](https://mintcdn.com/soloone/9HdwBb7FHoPnTmO1/images/dashboard/journeys/furnishing/furnishing-complete/06-product-furnish-upload-success.png?fit=max\&auto=format\&n=9HdwBb7FHoPnTmO1\&q=85\&s=a33c50d56a51b6ce19ae0d9fc2b7eb77) ![File uploads list (ingest status)](https://mintcdn.com/soloone/9HdwBb7FHoPnTmO1/images/dashboard/journeys/furnishing/furnishing-complete/09-uploads-list.png?fit=max\&auto=format\&n=9HdwBb7FHoPnTmO1\&q=85\&s=64c5f006b9eb53253c5410e518ea64dd) ## When to use a different channel * **One record at a time, in real time** — use the product furnish endpoints, e.g. `POST /v1/products/kyc_certificate_snapshot/furnish`. See the [furnishing overview](/api-overview/furnishing/overview). * **Recurring drops from systems that produce files natively** — use [SFTP](/api-overview/sftp/getting-started). Same files, same pipeline, no dashboard session required. > **[SFTP Getting Started](/api-overview/sftp/getting-started)** > > Deliver JSONL files on a schedule. > **[Furnishing Policies](/concepts/governance/furnishing-policies)** > > How mappings turn records into network data. # Network contribution Source: https://docs.solo.one/api-overview/furnishing/overview How contributing verified work to the network works — and what a contributor keeps **Furnishing** is how a participant contributes verified work into a network. It's the write half of the network: a verification your institution already performed becomes a reusable [trust asset](/concepts/trust/trust-assets) that entitled participants can later query. If querying is how you take value out of a SOLO network, contributing is how you put value in — and how you earn the right to take value out in the first place. ## Contributing is asset creation, not data donation The natural fear of contributing is "I'm giving away proprietary customer intelligence and losing control." A SOLO network is designed so that isn't what happens. When you furnish a verification, it does not vanish into a vendor database — it becomes an **attributed, governed asset** you still have a stake in: * **You keep attribution.** Every certificate sub-product your data backs carries your `furnishing_entity_id` and `attestation_event_id`. The verification travels with your name on it — see [Provenance](/concepts/trust/provenance). * **You keep governance.** Contributed data is still gated by the subject's [consent](/concepts/identity/consent), the network's [querying policy](/concepts/governance/querying-policies), and each reader's [entitlement](/concepts/governance/entitlement). Furnishing does not expose your data to passive observers. * **You gain read access.** Contributing is how you earn entitlement to read a subject back later (see below). ## Why furnish? Banks furnish for two reasons, one regulatory and one economic: 1. **It's how the network has anything to say.** Every certificate, attestation, and screening result a querier receives traces back to a record some furnisher contributed. A network where nobody furnishes is an empty database with a governance layer. 2. **It earns you read access.** Furnishing an entity makes you [entitled](/concepts/governance/entitlement) to that entity's data on future queries. Contributing is one of the two ways to earn entitlement — the other is a prior query. For most participants, furnishing the entities you already onboard is by far the cheapest way to build broad read coverage across the network. This earn-back loop is deliberate. Networks stay healthy when read access is proportional to contribution, so the [entitlement model](/concepts/governance/entitlement) treats every furnish as a deposit against future reads. ## The three furnishing channels There are three ways to get records into a network. All three converge on the same ingestion machinery — the differences are in transport, batch size, and how much automation you want to build. | | REST furnish | Dashboard upload | SFTP drops | | - | - | - | - | | **Endpoint / transport** | `POST /v1/products/{product}/furnish` | The SOLO dashboard's upload dialog | SFTP server (`sftp.solo.one`) | | **Payload** | JSON records in the request body | One [JSONL file](/api-overview/furnishing/file-upload) per upload | JSONL files dropped into `jsonl_furnish_data/` | | **Batch size** | One to a few records per call | One file, many records | One file, many records; many files per session | | **Processing** | Synchronous — response reflects the furnish | Asynchronous — tracked on the Uploads page | Asynchronous — ingestion triggers on upload | | **Best for** | Real-time furnishing from your onboarding flow | Occasional batches and backfills | Recurring scheduled drops from core-banking or data-warehouse exports | When to choose each: * **REST furnish** when furnishing is an event in your system — a consumer finishes onboarding, you furnish their record in the same workflow. Lowest latency, immediate feedback, no file handling. * **[Dashboard upload](/api-overview/furnishing/file-upload)** when you have a file in hand — a one-off backfill or a periodic batch you'd rather not automate. * **[SFTP](/api-overview/sftp/overview)** when your data already leaves your systems as files on a schedule. Most core-banking and data-warehouse stacks can target an SFTP endpoint with zero custom code, which makes it the usual choice for nightly or monthly drops. > [!NOTE] > > All three channels write the same network data. A record furnished over REST > and the same record uploaded in a JSONL file end up identical in the network — > same entity matching, same attribution, same entitlement effect. ## How a furnish works A furnish brings together a **network**, your **records**, and the **consumer** (or business) they describe: ```mermaid theme={null} flowchart LR Rec[Your records] --> F[Furnish] N[Network] --> F F --> M[Matched to entity] M --> Store[(Network data)] ``` | Concept | Role in the furnish | | - | - | | **Product** | *What* you're contributing over REST (e.g. a KYC certificate). Determines the endpoint and record schema. | | **Network** | *Where* the data is contributed. You must be a [furnisher](/concepts/governance/network-roles) of it. | | **Mapping** | For files only: the network's [furnishing policy](/concepts/governance/furnishing-policies) that claims each record's `_type` and turns it into network data. | | **Consumer / Business** | *Who* the data is about — matched from the identifiers in each record. | ### Anatomy of a REST furnish You call the product's furnish endpoint with the network scope and one or more records. For a KYC certificate: ```bash theme={null} curl -X POST https://api.solo.one/v1/products/kyc_certificate_snapshot/furnish \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "network_id": "9f1c0c2e-7a44-4f1e-9d2a-08b6f3a1c5d7", "application_date": "2026-05-28", "records": [ { "first_name": "Jane", "last_name": "Doe", "date_of_birth": "1990-01-15", "social_security_number": "123-45-6789" } ] }' ``` A successful furnish returns a submission acknowledgment: ```json theme={null} { "success": true, "submission_id": "5b2e9f10-3c7d-4e8a-b1f6-2d9c0a47e831" } ``` Field by field: * `network_id` — the network you're furnishing into. You must hold the furnisher role there. The certificate is written straight into this network; no furnishing policy is involved. * `application_date` — when the consumer applied. Required. * `records` — one or more consumer records. For KYC certificates each record carries `first_name`, `last_name`, `date_of_birth`, and `social_security_number` — all strings. The SSN is the matching key that connects your record to a consumer [entity](/concepts/identity/entities). The same shape applies to `POST /v1/products/kyb_certificate_snapshot/furnish` for business records. ### How file records reach a mapping You never name a mapping in a file. Each record's `_type` does it: the network's active [furnishing policies](/concepts/governance/furnishing-policies) (mappings) each claim a record type, and every record goes to each mapping that claims its type. Records no mapping claims are counted as unmatched — skipped without being treated as errors. ```mermaid theme={null} flowchart TD R[File record: _type] --> PM[The network's active mappings] PM -->|a mapping claims the type| RUN[Ingest under each claiming mapping] PM -->|no mapping claims it| UN[Unmatched — skipped, not an error] ``` As a furnisher you state *facts* (network, record type, record); the governor's mappings decide *rules*. See [record types](/api-overview/furnishing/file-upload#record-types) for the details. ## What happens after ingest Once a record is accepted, the furnishing pipeline takes over: 1. **Validation.** REST records are checked against the product's record schema; file records against the mapping that claimed them. Each record is processed independently — one bad record in a batch doesn't fail its neighbors. 2. **Entity matching.** Identifiers in the record (SSN for consumers; tax identifier and jurisdiction for businesses) are matched to an existing [entity](/concepts/identity/entities), or a new entity profile is created. Re-furnishing the same entity updates the existing profile rather than creating a duplicate. 3. **Records become network data.** The furnished attributes are written as that entity's data, attributed to your organization as furnisher. 4. **Certificates and attestations.** For certificate products, the furnished operations (document capture, address verification, and so on) become the evidence behind KYC/KYB certificates that queriers later receive. Where your organization has attested to the data, the attestation is linked to the resulting certificates. 5. **Entitlement is recorded.** Your organization becomes [entitled](/concepts/governance/entitlement) to this entity's data on future queries. ## Data quality expectations The network is only as good as what's furnished into it. A few expectations hold across every channel: * **Identifiers must be real and complete.** SSNs, EINs, and dates of birth are matching keys. A typo doesn't just corrupt one record — it creates a phantom entity or pollutes a real one. * **Treat identifiers as text.** SSNs and tax identifiers are strings, not integers. Exporting them as JSON numbers silently drops leading zeros. * **Furnish what the certificate needs.** Certificates are built from specific verification operations. Records that don't carry them won't produce certificates, even if they ingest cleanly. * **Idempotency is on natural keys.** Re-submitting a consumer record with the same SSN updates rather than duplicates. Don't "fix" a bad record by furnishing a second one with a tweaked identifier. ## Furnishing vs. querying | | Furnishing | Querying | | - | - | - | | Direction | Write into the network | Read from the network | | Needs consent? | No | Yes | | Needs a policy you pass? | No (file records route by `_type`) | Yes (`network_policy`) | | Earns entitlement? | Yes | Yes | See [Querying](/api-overview/querying/overview) for the read half. ## Troubleshooting > **403 / not authorized to furnish** > > Your organization must hold the furnisher role in the target network. See > [network roles](/concepts/governance/network-roles). Check that > `network_id` is the network where you were granted the role — roles don't > carry across networks. > **File accepted but no certificate appears** > > Most often the records were **unmatched**: no active mapping in the target > network claims their `_type`. Unmatched records are skipped deliberately, > not errored. The upload's results name the unmatched types — compare them > with the record types your network's feeds declare. > **Validation errors on individual records** > > Records are validated independently, so a batch can partially succeed. > Common causes: missing required fields (all four KYC record fields are > required), malformed dates (use ISO 8601, e.g. `1990-01-15`), or empty > identifier strings. > **Duplicate entities after furnishing** > > Entity matching keys on identifiers (SSN; EIN + jurisdiction). If you see > duplicates, the identifiers differed between submissions — check for > formatting drift such as dropped leading zeros or embedded whitespace. ## In the dashboard ![Furnishing channels — REST API, SFTP file drops, and data lake connectors](https://mintcdn.com/soloone/9HdwBb7FHoPnTmO1/images/dashboard/journeys/sources/furnishing-complete/01-list.png?fit=max\&auto=format\&n=9HdwBb7FHoPnTmO1\&q=85\&s=d05aca00353a37c0b3dfa7b59185e06a) ![Product furnish — JSONL upload dialog](https://mintcdn.com/soloone/9HdwBb7FHoPnTmO1/images/dashboard/journeys/furnishing/furnishing-complete/04-product-furnish-dialog.png?fit=max\&auto=format\&n=9HdwBb7FHoPnTmO1\&q=85\&s=d4b949e3c0c6d3cb54725e2b2501a41f) ![Furnish events history list](https://mintcdn.com/soloone/9HdwBb7FHoPnTmO1/images/dashboard/journeys/furnishing/furnishing-complete/07-history-list.png?fit=max\&auto=format\&n=9HdwBb7FHoPnTmO1\&q=85\&s=2c0807b0187f92c0de9bfbba9b0976dc) ![Furnish event detail — overview tab](https://mintcdn.com/soloone/9HdwBb7FHoPnTmO1/images/dashboard/journeys/furnishing/furnishing-complete/08-history-event-detail.png?fit=max\&auto=format\&n=9HdwBb7FHoPnTmO1\&q=85\&s=81f7cc8fed41aabacead52ec6d2273f7) ## Related concepts > **[JSONL Files](/api-overview/furnishing/file-upload)** > > Furnish whole files of records from the dashboard. > **[SFTP Ingestion](/api-overview/sftp/overview)** > > Recurring JSONL drops, no HTTP client required. > **[Furnishing Policies](/concepts/governance/furnishing-policies)** > > The rules the network applies to what you contribute. > **[Furnish an entity](/home/quickstart/furnish-an-entity)** > > A hands-on walkthrough of your first furnish. # The Index Microservice Source: https://docs.solo.one/api-overview/index-microservice/overview Connect your data warehouse by indexing its schema — without exposing row values > [!NOTE] > > **Available to registered partners only.** The Index Microservice is an > emerging capability provisioned per partner. Contact your SOLO representative > to discuss access before designing an integration. ## What it is The **Index Microservice** lets you take part in a network using the data you *already have*, without sending that data to SOLO. Instead of exporting records, you connect your data warehouse and SOLO looks only at its **structure** — the table and column layout — to learn what kinds of data you hold. Your actual records stay in your warehouse; SOLO never reads the values during indexing. In short: you point SOLO at your warehouse, SOLO learns the *shape* of your data and lines it up with the [SOLO schema](/products/overview), and from then on your existing data can serve the network in place. ## Why it's useful * **No data export.** The raw records never leave your systems, which sidesteps the cost and the data-handling concerns of copying sensitive data to a vendor. * **No row values exposed.** Indexing reads schema and metadata only — names, types, and shape — not the contents of any row. * **Use what you already maintain.** If your verification data already lives in Databricks, Snowflake, BigQuery, or similar, you can contribute from it directly rather than building and running an export pipeline. ## When and why you'd use it | Situation | Why the Index Microservice fits | | - | - | | **You hold large existing datasets** | Connect once and map, instead of continuously pushing records to SOLO. | | **Exporting data is a non-starter** | Your records stay put; only schema and metadata are indexed. | | **You already run a warehouse** | Reuse the data and access controls you maintain today. | | **You want low-maintenance participation** | After the one-time connect and mapping, there's no per-record or per-file submission to operate. | If instead you want to push data as events or scheduled files, use the direct [furnishing channels](/api-overview/furnishing/overview) (REST, bulk upload, or SFTP). The Index Microservice is the option for *leaving the data where it is*. ## How it works The Index Microservice is effectively a fourth way to get data into a network, alongside the three direct [furnishing](/api-overview/furnishing/overview) channels. The difference is *where the data lives*: | | REST / Bulk / SFTP furnish | Index Microservice | | - | - | - | | **Data movement** | You push records to SOLO | Data stays in your warehouse | | **What SOLO sees** | The records you send | Schema and metadata only | | **Setup** | Per-record or per-file submission | One-time connect + attribute mapping | | **Best for** | Event-driven or batch contribution | Large existing datasets you don't want to export | ```mermaid theme={null} flowchart LR WH[(Your data warehouse
Databricks · Snowflake · BigQuery)] WH -->|schema + metadata only| IDX[Index Microservice] IDX -->|map columns to SOLO schema| Map[Attribute mapping] Map --> Net[(SOLO network)] ``` **1. Connect your warehouse** Authorize a connection scoped to schema and metadata reads only — no row-value access. **2. Scan the schema** SOLO indexes the structure of the connected source: tables, columns, and types. **3. Map to the SOLO schema** Map your indexed columns onto SOLO models and fields, so the network knows how your data corresponds to its shared vocabulary. **4. Participate** Once mapped, your indexed source can back the network's products under the same [governance](/concepts/governance/networks) — consent, policy, and entitlement — as any other contribution. ### Governance still applies Indexing a source does not bypass the network's controls. Everything served through an indexed source remains gated by the subject's [consent](/concepts/identity/consent), the network's [querying policy](/concepts/governance/querying-policies), and each reader's [entitlement](/concepts/governance/entitlement) — exactly as with directly furnished data. ## Related concepts > **[Furnishing API](/api-overview/furnishing/overview)** > > The direct channels for contributing data. > **[Products](/products/overview)** > > The SOLO schema your columns map onto. > **[Querying policies](/concepts/governance/querying-policies)** > > The rules that govern what an indexed source can serve. > **[Entitlement](/concepts/governance/entitlement)** > > Who can read what, regardless of where the data lives. # Coverage Check Source: https://docs.solo.one/api-overview/querying/coverage-check Non-billable soft check of field coverage before running a query The **coverage check** is a non-billable, read-only pre-flight for product queries. Given a product, a subject, and a network + policy scope, it reports — **per furnisher** — which of the policy's required fields are covered by data already furnished to the network. It answers *"if I run this query now, can it succeed?"* without issuing a certificate, recording a query event, or creating a billable event. ```text theme={null} POST /v1/products/check ``` One endpoint serves every product: pass the `product_id` of the product you intend to query. ## Why it exists A product query that resolves but cannot satisfy the policy returns [`204 No Content`](/api-overview/querying/overview#200-vs-204-here-is-data-vs-nothing-usable) — and that resolved query is still a billable event. The coverage check lets you avoid predictable `204`s: ```mermaid theme={null} sequenceDiagram participant App as Your app participant API as SOLO API App->>API: POST /v1/products/check (non-billable) API-->>App: per-furnisher coverage alt coverage complete App->>API: POST /v1/products/{product}/query (billable) API-->>App: 200 + certificate else coverage incomplete App->>App: skip query / adjust UX / pick another policy end ``` ## Request ```bash theme={null} curl -X POST https://api.solo.one/v1/products/check \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "product_id": "3e8a1d5f-…", "consent_id": "a3f0b9c7-…", "policy_id": "5e7d2a14-…", "network_ids": ["9f1c0c2e-…"] }' ``` | Field | Type | Required | Purpose | | - | - | - | - | | `product_id` | UUID | yes | The product to coverage-check. | | `consent_id` | string | one of¹ | Consent token — the system resolves the subject's identity. | | `consumer_identity_id` | UUID | one of¹ | Direct consumer profile reference (for consumer-targeted products). | | `business_identity_id` | UUID | one of¹ | Direct business profile reference (for business-targeted products). | | `network_ids` | UUID\[] | yes | Networks to check across. | | `policy_id` | UUID | no | The [querying policy](/concepts/governance/querying-policies) whose requirements to check against. If omitted, each network's default policy is used. | | `furnishing_entity_ids` | UUID\[] | no | Restrict the check to specific furnishers. When omitted, all furnishers are considered. | ¹ Identify the subject the same way you would on the query itself: a `consent_id`, or the direct profile id matching the product's target (`consumer_identity_id` for consumer products, `business_identity_id` for business products). ## Response A `200 OK` with one entry per furnisher that has relevant data: ```json theme={null} { "product_id": "3e8a1d5f-…", "consumer_identity_id": "c2a4e8d0-…", "business_identity_id": null, "entities": [ { "furnishing_entity_id": "e1d2c3b4-…", "furnishing_entity_name": "First Example Bank", "network_id": "9f1c0c2e-…", "complete": false, "models": [ { "model_name": "DocumentCaptureEvent", "met_count": 2, "total_count": 2, "fields": [ { "field_name": "is_document_captured", "met": true }, { "field_name": "document_capture_timestamp", "met": true } ] }, { "model_name": "LivenessCheckEvent", "met_count": 0, "total_count": 1, "fields": [ { "field_name": "is_liveness_captured", "met": false } ] } ] } ] } ``` | Field | Meaning | | - | - | | `entities[]` | One row per furnisher whose data was evaluated. | | `entities[].complete` | Whether this furnisher's data alone satisfies every checked requirement. | | `entities[].models[]` | Per-model breakdown, keyed by the product's model names (see each product's field reference). | | `models[].met_count` / `total_count` | How many of the model's checked fields are covered. | | `models[].fields[]` | Field-level detail: each `field_name` and whether it's `met`. | The `field_name` values trace directly to the product's field reference tables — see the [KYC certificate](/api-overview/querying/kyc-certificate-snapshot#field-reference) and [KYB certificate](/api-overview/querying/kyb-certificate-snapshot#field-reference) pages. If the `product_id` cannot be resolved, the endpoint returns `404` with the standard error envelope (`{"detail": "…", "error_code": "RESOURCE_NOT_FOUND"}`) — see [Errors](/home/errors). ## When to use it * **Pre-flight UX.** Before showing a "verify with SOLO" path during onboarding, check whether the network can actually produce a certificate for this subject — and degrade gracefully if not, rather than surfacing a dead end. * **Avoiding empty 204s.** A billable query that returns `204` tells you the policy's requirements weren't met *after* the fact. The coverage check tells you the same thing for free, before you spend the query. * **Choosing a scope.** The per-furnisher, per-network breakdown shows *which* furnisher's data is complete, so you can target `furnishing_entity_ids` or pick the right network — or a different policy — before querying. * **Diagnosing a 204 you already received.** Run the same scope through the coverage check to see exactly which model's fields fell short. ## What it does not do > [!WARNING] > > The coverage check is a *soft* check. It does **not**: > > * issue a certificate or return any subject data — only coverage booleans and > counts; > * record a query event — there is no `X-Ref-Id` to quote, and nothing is added > to the billable audit trail; > * create a billable event; > * guarantee the subsequent query returns `200` — data can change between the > check and the query, and the final result is still shaped by > [entitlement](/concepts/governance/entitlement) and the > [policy](/concepts/governance/querying-policies) at query time. ## Related > **[Querying](/api-overview/querying/overview)** > > The billable query the coverage check pre-flights. > **[Products](/api-overview/querying/products)** > > The catalog of products you can check and query. # KYB Certificate Source: https://docs.solo.one/api-overview/querying/kyb-certificate-snapshot Business-verification certificate for business onboarding The **KYB Certificate** is a reusable Know Your Business attestation that packages UBO (ultimate beneficial owner), incorporation, and business-identity evidence into a single certificate. Rather than each institution repeating registry lookups, ownership tracing, and risk screening for the same business, a query assembles the verification work already furnished by network participants into one network-issued result. | | | | - | - | | **Category** | Identity Verification | | **Use case** | Customer Onboarding | | **Subject** | Business | | **Operations** | `POST /v1/products/kyb_certificate_snapshot/query`, `POST /v1/products/kyb_certificate_snapshot/furnish` | ## What's in the certificate A KYB certificate consolidates three **sub-products**, each a block in the query response with its own `assertions` (what was attested, and when) and `data` (the supporting attributes): | Sub-product | Response key | What it attests | | - | - | - | | Business identity verification | `business_identity_verification` | The legal entity exists and is what it claims — registration, jurisdiction, tax ID validation, operational existence. | | Ownership & control verification | `business_ownership_control_verification` | Beneficial owners, control persons, and authorized representatives were identified and evidenced. | | Risk & compliance assessment | `business_risk_compliance_assessment` | Sanctions, adverse media, restricted-activity, and activity-risk screening were performed. | Every populated sub-product carries the `furnishing_entity_id` of the participant whose data backed it and the `attestation_event_id` of their attestation. Shared business descriptors — DBA name, website, jurisdiction of formation, registration identifier — are repeated in each sub-product's `data` block so each block stands alone. ## Querying ```bash theme={null} curl -X POST https://api.solo.one/v1/products/kyb_certificate_snapshot/query \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "consent_id": "a3f0b9c7-…", "policy_id": "5e7d2a14-…", "network_ids": ["9f1c0c2e-…"] }' ``` The request follows the standard [query anatomy](/api-overview/querying/overview): identify the subject with `consent_id` (or `business_identity_id` for direct permissible-purpose lookups), and scope the read with `network_ids` plus an optional `policy_id` and `furnishing_entity_ids`. A `200 OK` means a certificate was issued: ```json theme={null} { "certificate_id": "8d4b2f6a-…", "provenance_event_id": "7b2d9c4e-…", "business_identity_id": "b9e1f7c3-…", "result": { "meta": { "network_id": "9f1c0c2e-…", "policy_id": "5e7d2a14-…" }, "business_identity_verification": { "furnishing_entity_id": "e1d2c3b4-…", "attestation_event_id": "f0a1b2c3-…", "assertions": { "business_identity_verification_assertion": true, "business_identity_verification_performed_timestamp": "2026-05-02T00:00:00Z" }, "data": { "business_formation_document_artifact": "articles_of_organization.pdf", "business_dba_name": "Acme Coffee", "business_website_url": "https://acme.example", "business_jurisdiction_of_formation": "DE", "business_registration_identifier": "7423918", "business_identity_verification_sources": ["primary_government_registry"], "business_entity_type": "llc", "business_registration_status": "active", "business_operational_existence_status": "confirmed_registered_and_operating", "business_tax_id_validation_result": "match_confirmed", "business_address_verification_result": "verified_registered_and_operating_address", "business_registered_address": null, "business_operating_address": null } }, "business_ownership_control_verification": { "furnishing_entity_id": "e1d2c3b4-…", "attestation_event_id": "f0a1b2c3-…", "assertions": { "business_ownership_control_verification_assertion": true, "business_ownership_control_verification_timestamp": "2026-05-03T00:00:00Z" }, "data": { "business_ownership_evidence_artifact": "cap_table.pdf", "business_dba_name": "Acme Coffee", "business_website_url": "https://acme.example", "business_jurisdiction_of_formation": "DE", "business_registration_identifier": "7423918", "business_ownership_control_verification_overall_status": "complete", "business_ownership_control_verification_overall_primary_source": "primary_government_registry", "business_authority_determination_basis": "operating_or_partnership_agreement", "business_authorized_representatives_identified_count": 2, "business_beneficial_owners_identified_count": 1, "business_beneficial_ownership_determination_method": "ownership_percentage_rule", "business_beneficial_ownership_threshold_applied": "25_percent", "business_control_authority_evidence_reviewed_type": "governing_legal_document", "business_control_determination_basis": "governance_role_control", "business_control_persons_identified_count": 1, "business_ownership_evidence_reviewed_type": "governing_legal_document" } }, "business_risk_compliance_assessment": { "furnishing_entity_id": "e1d2c3b4-…", "attestation_event_id": "f0a1b2c3-…", "assertions": { "business_risk_compliance_verification_assertion": true, "business_risk_compliance_timestamp": "2026-05-03T00:00:00Z" }, "data": { "business_dba_name": "Acme Coffee", "business_website_url": "https://acme.example", "business_jurisdiction_of_formation": "DE", "business_registration_identifier": "7423918", "business_primary_activity_classification_code": "722515", "business_primary_activity_classification_system": "naics", "business_activity_risk_level": "low_risk", "business_sanctions_screening_result": "no_match", "business_adverse_media_assessment_result": "no_adverse_media", "business_compliance_screening_scope_applied": "business_and_all_beneficial_owners", "business_restricted_activity_assessment_result": "no_restricted_activity", "business_risk_compliance_assessment_overall_primary_source": "commercial_verification_provider", "business_risk_compliance_assessment_overall_status": "verified", "business_risk_compliance_assessment_timestamp": "2026-05-03T00:00:00Z" } } } } ``` * `certificate_id` — the issued certificate, recording its as-of date, attestation timestamp, and the network + policy pairs it was resolved under. * `provenance_event_id` — the billable provenance event id, also returned in the `X-Ref-Id` response header. * `result` — the consolidated certificate. Sub-products without qualifying data are `null`. ## How the certificate resolves per network The query gathers furnished business events across every network in `network_ids`, applies the querying policy's filters uniformly, and selects the **oldest matching event** from any allowed network for each sub-product. The result's `meta.network_id` is anchored to the **first** network in your request — list your primary network first. Which fields you ultimately see is shaped by the [querying policy](/concepts/governance/querying-policies) and your [entitlement](/concepts/governance/entitlement). ## When you get a 204 The endpoint declares a `204 No Content` response: *no certificate could be created — the available data did not satisfy the policy requirements*. You'll receive a `204` when: * a required sub-product (identity, ownership & control, or risk/compliance) was never furnished for this business in the queried networks, or * furnished events exist but fail the policy's filters (e.g. verification older than the policy's freshness window), or * the policy selected specific data fields that the resolved certificate could not populate. The body is empty; the `X-Ref-Id` header is still present and the query event is still recorded. Run a [coverage check](/api-overview/querying/coverage-check) first to anticipate `204`s without spending a billable query. ## Furnishing Participants contribute KYB data with `POST /v1/products/kyb_certificate_snapshot/furnish`: ```json theme={null} { "network_id": "9f1c0c2e-…", "application_date": "2026-05-28", "records": [ { "business_legal_name": "Acme Coffee LLC", "business_ein": "12-3456789" } ] } ``` The response is `{"success": true, "submission_id": "…"}`. Bulk contribution is also available via [file upload](/api-overview/furnishing/file-upload) and [SFTP](/api-overview/sftp/overview); see [Furnishing](/api-overview/furnishing/overview) for the full model. ## Field reference The KYB certificate's data dictionary, model by model. *Field* is the display name, *API name* is the `field_name` used in policies and coverage checks, and *Source model* is the underlying table the value is drawn from. ### BusinessIdentity | Field | API name | Type | Source model | | - | - | - | - | | Business Legal Name | `business_legal_name` | String | `business_identity` | ### BusinessIdentityVerificationEvent | Field | API name | Type | Source model | | - | - | - | - | | Attestation ID | `attestation_event_id` | UUID | `business_identity_verification_event` | | Furnishing Entity ID | `furnishing_entity_id` | UUID | `attestation_event` | | Is Identity Verified | `is_identity_verified` | Boolean | `business_identity_verification_event` | | Identity Verification Timestamp | `identity_verification_timestamp` | Date | `business_identity_verification_event` | | Identity Verification Overall Status | `identity_verification_overall_status` | String | `business_identity_verification_event` | | Identity Verification Overall Primary Source | `identity_verification_overall_primary_source` | String | `business_identity_verification_event` | | Is Secretary Of State Match | `is_secretary_of_state_match` | Boolean | `business_identity_verification_event` | | Operational Existence Status | `operational_existence_status` | String | `business_identity_verification_event` | | Tax ID Validation Result | `tax_id_validation_result` | String | `business_identity_verification_event` | | Tax Identifier Type | `tax_identifier_type` | String | `business_identity_verification_event` | | Tax Identifier Value | `tax_identifier_value` | String | `business_identity_verification_event` | | Address Verification Result | `address_verification_result` | String | `business_identity_verification_event` | | DBA Name | `dba_name` | String | `business_identity_verification_event` | | Formation Document Artifact | `formation_document_artifact` | String | `business_identity_verification_event` | | Jurisdiction Of Formation | `jurisdiction_of_formation` | String | `business_identity_verification_event` | | Registration Identifier | `registration_identifier` | String | `business_identity_verification_event` | | Registration Status | `registration_status` | String | `business_identity_verification_event` | | Website URL | `website_url` | String | `business_identity_verification_event` | ### BusinessOwnershipControlVerificationEvent | Field | API name | Type | Source model | | - | - | - | - | | Attestation ID | `attestation_event_id` | UUID | `business_ownership_control_verification_event` | | Furnishing Entity ID | `furnishing_entity_id` | UUID | `attestation_event` | | Is Ownership Control Verified | `is_ownership_control_verified` | Boolean | `business_ownership_control_verification_event` | | Ownership Control Verification Timestamp | `ownership_control_verification_timestamp` | Date | `business_ownership_control_verification_event` | | Ownership Control Verification Overall Status | `ownership_control_verification_overall_status` | String | `business_ownership_control_verification_event` | | Ownership Control Verification Overall Primary Source | `ownership_control_verification_overall_primary_source` | String | `business_ownership_control_verification_event` | | Authority Determination Basis | `authority_determination_basis` | String | `business_ownership_control_verification_event` | | Authorized Representatives Identified Count | `authorized_representatives_identified_count` | Integer | `business_ownership_control_verification_event` | | Beneficial Owners Identified Count | `beneficial_owners_identified_count` | Integer | `business_ownership_control_verification_event` | | Beneficial Ownership Determination Method | `beneficial_ownership_determination_method` | String | `business_ownership_control_verification_event` | | Beneficial Ownership Threshold Applied | `beneficial_ownership_threshold_applied` | String | `business_ownership_control_verification_event` | | Control Authority Evidence Reviewed Type | `control_authority_evidence_reviewed_type` | String | `business_ownership_control_verification_event` | | Control Determination Basis | `control_determination_basis` | String | `business_ownership_control_verification_event` | | Control Persons Identified Count | `control_persons_identified_count` | Integer | `business_ownership_control_verification_event` | | Is Personally Guaranteed | `is_personally_guaranteed` | Boolean | `business_ownership_control_verification_event` | | Ownership Evidence Artifact | `ownership_evidence_artifact` | String | `business_ownership_control_verification_event` | | Ownership Evidence Reviewed Type | `ownership_evidence_reviewed_type` | String | `business_ownership_control_verification_event` | ### BusinessRiskComplianceEvent | Field | API name | Type | Source model | | - | - | - | - | | Attestation ID | `attestation_event_id` | UUID | `business_risk_compliance_event` | | Furnishing Entity ID | `furnishing_entity_id` | UUID | `attestation_event` | | Is Risk Compliance Assessed | `is_risk_compliance_assessed` | Boolean | `business_risk_compliance_event` | | Risk Compliance Assessment Timestamp | `risk_compliance_assessment_timestamp` | Date | `business_risk_compliance_event` | | Risk Compliance Assessment Overall Status | `risk_compliance_assessment_overall_status` | String | `business_risk_compliance_event` | | Risk Compliance Assessment Overall Primary Source | `risk_compliance_assessment_overall_primary_source` | String | `business_risk_compliance_event` | | Activity Risk Level | `activity_risk_level` | String | `business_risk_compliance_event` | | Sanctions Screening Result | `sanctions_screening_result` | String | `business_risk_compliance_event` | | Adverse Media Assessment Result | `adverse_media_assessment_result` | String | `business_risk_compliance_event` | | Compliance Screening Scope Applied | `compliance_screening_scope_applied` | String | `business_risk_compliance_event` | | Restricted Activity Assessment Result | `restricted_activity_assessment_result` | String | `business_risk_compliance_event` | | Primary Activity Classification Code | `primary_activity_classification_code` | String | `business_risk_compliance_event` | | Primary Activity Classification System | `primary_activity_classification_system` | String | `business_risk_compliance_event` | ### KYBCertificateSnapshot (policy configuration only) These certificate-level fields are used when configuring a [querying policy](/concepts/governance/querying-policies) — filters over issued certificates, not data projected into the query response. | Field | API name | Type | Source model | | - | - | - | - | | Certificate As Of Date | `certificate_as_of_date` | Date | `kyb_certificate_snapshot` | | Certificate Attestation Timestamp | `certificate_attestation_timestamp` | Datetime | `kyb_certificate_snapshot` | | Days Since Certificate As Of Date | `days_since_certificate_as_of_date` | Integer | `kyb_certificate_snapshot` | | Is Business Identity Verification Performed | `is_business_identity_verification_performed` | Boolean | `kyb_certificate_snapshot` | | Is Business Ownership Control Performed | `is_business_ownership_control_performed` | Boolean | `kyb_certificate_snapshot` | | Is Business Risk Compliance Assessed | `is_business_risk_compliance_assessed` | Boolean | `kyb_certificate_snapshot` | ## Related ## In the dashboard ![Run query — choose consumer or business](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/04-wizard-step-kind.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=e626158a43f65f7522ecb5891e4e8464) ![Run query — choose KYB certificate product](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/19-wizard-step-kyb-product.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=3e27128dc13fa314b54e4e8266264acd) ![Run query — choose policy](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/20-wizard-step-kyb-policy.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=5d526fae66128bf438ba4d66485f03c0) ![Run query — choose business](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/21-wizard-step-kyb-entity.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=370fbf56f9cc2c84e30eb07aa5b3ca61) ![Run query — networks and consent matrix](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/22-wizard-step-kyb-networks.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=4917ab4b324adc88a84755d60d482c9a) ![Business KYB query — policy and billing](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/23-business-kyb-result-policy-billing.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=e7c33ece5c7d9581c450ac1e69546ae2) ![Business KYB query — REST response payload](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/24-business-kyb-result-response.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=53ede6a63d002c413dc1d5cfb726ed60) ![Business — query history tab](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/entities/policies-and-entities-complete/29-business-tab-queries.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=1231a019bf9e2061990d3465ab196211) > **[Querying](/api-overview/querying/overview)** > > Request anatomy, 200 vs 204, billing, and the X-Ref-Id header. > **[Coverage check](/api-overview/querying/coverage-check)** > > Check field coverage before running a billable query. # KYC Certificate Source: https://docs.solo.one/api-overview/querying/kyc-certificate-snapshot Identity-verification certificate for consumer onboarding The **KYC Certificate** is a reusable identity-verification certificate bundle for consumer onboarding. It replaces per-bank KYC intake with a network-issued attestation: instead of each institution re-running document capture, biometric checks, and identity corroboration for the same person, a query assembles the verification work already furnished by network participants into a single certificate. | | | | - | - | | **Category** | Identity Verification | | **Use case** | Customer Onboarding | | **Subject** | Consumer | | **Operations** | `POST /v1/products/kyc_certificate_snapshot/query`, `POST /v1/products/kyc_certificate_snapshot/furnish` | ## What's in the certificate A KYC certificate consolidates up to nine **sub-products**, each a block in the query response with its own `assertions` (what was attested, and when) and `data` (the supporting attributes): | Sub-product | Response key | What it attests | | - | - | - | | Document capture | `document_capture` | An identity document was captured, with its type, number, and dates. | | Document review | `document_review` | The document's attributes were reviewed — tamper and machine-readable-data checks. | | Biometric capture | `biometric_capture` | A biometric artifact (e.g. a selfie) was captured. | | Biometric review | `biometric_review` | The biometric was compared against a reference, with quality and outcome. | | Liveness capture | `liveness_capture` | Liveness evidence was captured. | | Liveness review | `liveness_review` | The liveness evidence was reviewed, with outcome and confidence tier. | | Address capture | `address_capture` | A residential address was captured. | | Address verification | `address_verification` | The address was verified, with method, outcome, and deliverability. | | Identity corroboration | `identity_corroboration` | CIP-style corroboration — SSN, name, and DOB matches across sources. | Every populated sub-product carries the `furnishing_entity_id` of the participant whose data backed it and the `attestation_event_id` of their attestation, so the certificate is auditable down to its sources. ## Querying ```bash theme={null} curl -X POST https://api.solo.one/v1/products/kyc_certificate_snapshot/query \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "consent_id": "a3f0b9c7-…", "policy_id": "5e7d2a14-…", "network_ids": ["9f1c0c2e-…"] }' ``` The request follows the standard [query anatomy](/api-overview/querying/overview): identify the subject with `consent_id` (or `consumer_identity_id` for direct permissible-purpose lookups), and scope the read with `network_ids` plus an optional `policy_id` and `furnishing_entity_ids`. A `200 OK` means a certificate was issued: ```json theme={null} { "certificate_id": "1c9f4e2a-…", "provenance_event_id": "7b2d9c4e-…", "consumer_identity_id": "c2a4e8d0-…", "result": { "meta": { "network_id": "9f1c0c2e-…", "policy_id": "5e7d2a14-…" }, "document_capture": { "furnishing_entity_id": "e1d2c3b4-…", "attestation_event_id": "f0a1b2c3-…", "assertions": { "document_capture_assertion": true, "document_capture_timestamp": "2026-04-15T00:00:00Z" }, "data": { "document_artifact": "passport_scan.pdf", "document_type": "us_passport", "document_issuing_state": "NY", "document_number": "934712385", "document_issue_date": "2021-03-02", "document_expiration_date": "2031-03-02", "document_capture_method": "mobile_live_capture" } }, "document_review": { "furnishing_entity_id": "e1d2c3b4-…", "attestation_event_id": "f0a1b2c3-…", "assertions": { "document_attribute_review_assertion": true, "document_attribute_review_timestamp": "2026-04-16T00:00:00Z" }, "data": { "document_review_method": "automated_and_manual", "document_review_evidence_source": "document_issuer", "document_tamper_review_performed": true, "document_tamper_indicators_observed": false, "is_document_machine_readable_data_validation_performed": true, "is_document_machine_readable_data_validation_consistent_with_document_face": true } }, "biometric_capture": { "furnishing_entity_id": "e1d2c3b4-…", "attestation_event_id": "f0a1b2c3-…", "assertions": { "biometric_capture_assertion": true, "biometric_capture_timestamp": "2026-04-15T00:00:00Z" }, "data": { "biometric_artifact": "selfie.jpg", "biometric_capture_method": "live_selfie_video" } }, "identity_corroboration": { "furnishing_entity_id": "e1d2c3b4-…", "attestation_event_id": "f0a1b2c3-…", "assertions": { "is_identity_corroboration_performed": true, "identity_corroboration_timestamp": "2026-04-16T09:30:00Z" }, "data": { "identity_elements_corroborated": "ssn, dob, name", "identity_corroboration_methods": ["credit_bureau_match", "public_records_match"], "identity_corroboration_outcomes": ["full_match"], "total_identity_corroboration_sources_consulted": 2, "identity_corroboration_source_count_full_match": null, "identity_corroboration_source_count_inconclusive": null, "identity_corroboration_source_count_no_match": null, "identity_corroboration_source_count_partial_match": null } }, "biometric_review": null, "liveness_capture": null, "liveness_review": null, "address_capture": null, "address_verification": null } } ``` * `certificate_id` — the issued certificate. The certificate records the date it was issued, its attestation timestamp, and the network + policy pairs it was resolved under. * `provenance_event_id` — the billable provenance event id; the same value is returned in the `X-Ref-Id` response header. Quote it to support and keep it in your audit log. * `result` — the consolidated certificate. Sub-products the policy didn't require (or that lacked data) are `null`. The per-source outcome counts (`*_source_count_*`) are always `null`: furnishers report one outcome per check, not one per source consulted. ## How the certificate resolves per network ```mermaid theme={null} flowchart LR A[Resolve subject
consent_id / consumer_identity_id] --> B[Gather furnished events
across network_ids] B --> C[Apply querying policy
field + freshness filters] C --> D[Pick oldest matching event
per sub-product] D --> E{Policy requirements met?} E -- yes --> F[Issue certificate · 200] E -- no --> G[No certificate · 204] ``` The query gathers furnished events across every network in `network_ids` and applies the querying policy's filters uniformly. For each sub-product it selects the **oldest matching event** from any allowed network, and the result's `meta.network_id` is anchored to the **first** network in your request — list your primary network first. Which furnishers' data is considered, and which fields you see, is further shaped by your [entitlement](/concepts/governance/entitlement). ## When you get a 204 The endpoint declares a `204 No Content` response: *no certificate could be created — the available data did not satisfy the policy requirements*. Concretely, a `204` is returned when: * a sub-product the policy requires was never furnished for this consumer in the queried networks, or * furnished data exists but fails the policy's filters (e.g. outside the freshness window the policy selected), or * the policy selected specific data fields that the resolved certificate could not populate. The body is empty. The response still carries the `X-Ref-Id` header, and the query event is still recorded. To anticipate `204`s before spending a billable query, run a [coverage check](/api-overview/querying/coverage-check) first. ## Furnishing Participants contribute the underlying KYC data with `POST /v1/products/kyc_certificate_snapshot/furnish`: ```json theme={null} { "network_id": "9f1c0c2e-…", "application_date": "2026-05-28", "records": [ { "first_name": "Jane", "last_name": "Doe", "date_of_birth": "1990-01-15", "social_security_number": "123-45-6789" } ] } ``` The response is `{"success": true, "submission_id": "…"}`. Bulk contribution is also available via [file upload](/api-overview/furnishing/file-upload) and [SFTP](/api-overview/sftp/overview); see [Furnishing](/api-overview/furnishing/overview) for the full model. ## Field reference The KYC certificate's data dictionary, model by model. *Field* is the display name, *API name* is the `field_name` used in policies and coverage checks, and *Source model* is the underlying table the value is drawn from. ### ConsumerIdentity | Field | API name | Type | Source model | | - | - | - | - | | First Name | `first_name` | String | `consumer_identity` | | Last Name | `last_name` | String | `consumer_identity` | ### DocumentCaptureEvent | Field | API name | Type | Source model | | - | - | - | - | | Attestation ID | `attestation_event_id` | UUID | `document_capture_event` | | Furnishing Entity ID | `furnishing_entity_id` | UUID | `attestation_event` | | Is Document Captured | `is_document_captured` | Boolean | `document_capture_event` | | Document Capture Timestamp | `document_capture_timestamp` | Date | `document_capture_event` | | Is Document Attribute Reviewed | `is_document_attribute_reviewed` | Boolean | `document_capture_event` | | Document Attribute Review Timestamp | `document_attribute_review_timestamp` | Date | `document_capture_event` | | Document Capture Method | `document_capture_method` | String | `document_capture_event` | | Document Review Method | `document_review_method` | String | `document_capture_event` | | Document Review Evidence Source | `document_review_evidence_source` | String | `document_capture_event` | ### IdentityDocumentIdentity | Field | API name | Type | Source model | | - | - | - | - | | Identity Document Type | `type` | String | `identity_document_identity` | | Document Issue Date | `issue_date` | Date | `identity_document_identity` | | Document Expiration Date | `expiration_date` | Date | `identity_document_identity` | | Issuing Authority | `issuing_authority` | String | `identity_document_identity` | | Issuing State | `issuing_state` | String | `identity_document_identity` | | Is Document Tamper Review Performed | `is_document_tamper_review_performed` | Boolean | `identity_document_identity` | | Document Tamper Review Outcome | `document_tamper_review_outcome` | String | `identity_document_identity` | | Is Machine-Readable Data Validation Performed | `is_machine_readable_data_validation_performed` | Boolean | `identity_document_identity` | | Is Machine-Readable Data Consistent With Document Face | `is_machine_readable_data_consistent_with_document_face` | Boolean | `identity_document_identity` | | Are Security Features Verified | `are_security_features_verified` | Boolean | `identity_document_identity` | | Document Number | `number` | String | `identity_document_identity` | | Document Upload | `upload` | String | `identity_document_identity` | ### BiometricCaptureEvent | Field | API name | Type | Source model | | - | - | - | - | | Attestation ID | `attestation_event_id` | UUID | `biometric_capture_event` | | Furnishing Entity ID | `furnishing_entity_id` | UUID | `attestation_event` | | Is Biometric Captured | `is_biometric_captured` | Boolean | `biometric_capture_event` | | Biometric Capture Timestamp | `biometric_capture_timestamp` | Date | `biometric_capture_event` | | Is Biometric Attribute Reviewed | `is_biometric_attribute_reviewed` | Boolean | `biometric_capture_event` | | Biometric Attribute Review Timestamp | `biometric_attribute_review_timestamp` | Date | `biometric_capture_event` | | Biometric Capture Method | `biometric_capture_method` | String | `biometric_capture_event` | | Is Biometric Comparison Performed | `is_biometric_comparison_performed` | Boolean | `biometric_capture_event` | | Biometric Comparison Evidence Source | `biometric_comparison_evidence_source` | String | `biometric_capture_event` | | Biometric Comparison Reference | `biometric_comparison_reference` | String | `biometric_capture_event` | | Biometric Comparison Outcome | `biometric_comparison_outcome` | String | `biometric_capture_event` | | Biometric Artifact Type | `biometric_artifact_type` | String | `biometric_capture_event` | | Biometric Artifact Image Quality | `biometric_artifact_image_quality` | String | `biometric_capture_event` | | Biometric Artifact Subject Present | `biometric_artifact_subject_present` | Boolean | `biometric_capture_event` | | Biometric Artifact Upload | `biometric_artifact_upload` | String | `biometric_capture_event` | ### LivenessCheckEvent | Field | API name | Type | Source model | | - | - | - | - | | Attestation ID | `attestation_event_id` | UUID | `liveness_check_event` | | Furnishing Entity ID | `furnishing_entity_id` | UUID | `attestation_event` | | Is Liveness Captured | `is_liveness_captured` | Boolean | `liveness_check_event` | | Capture Timestamp | `capture_timestamp` | Date | `liveness_check_event` | | Is Liveness Evidence Reviewed | `is_liveness_evidence_reviewed` | Boolean | `liveness_check_event` | | Evidence Review Timestamp | `evidence_review_timestamp` | Date | `liveness_check_event` | | Capture Method Type | `capture_method_type` | String | `liveness_check_event` | | Liveness Capture Environment | `liveness_capture_environment` | String | `liveness_check_event` | | Liveness Review Evidence Source | `liveness_review_evidence_source` | String | `liveness_check_event` | | Check Result | `check_result` | String | `liveness_check_event` | | Evidence Clarity | `evidence_clarity` | String | `liveness_check_event` | ### AddressCaptureEvent | Field | API name | Type | Source model | | - | - | - | - | | Attestation ID | `attestation_event_id` | UUID | `address_capture_event` | | Furnishing Entity ID | `furnishing_entity_id` | UUID | `attestation_event` | | Is Address Captured | `is_address_captured` | Boolean | `address_capture_event` | | Address Capture Timestamp | `address_capture_timestamp` | Date | `address_capture_event` | | Is Address Verified | `is_address_verified` | Boolean | `address_capture_event` | | Address Verification Timestamp | `address_verification_timestamp` | Date | `address_capture_event` | | Address Verification Method Type | `address_verification_method_type` | String | `address_capture_event` | | Address Verification Outcome | `address_verification_outcome` | String | `address_capture_event` | | Deliverable | `deliverable` | Boolean | `address_capture_event` | ### IdentityCorroborationEvent | Field | API name | Type | Source model | | - | - | - | - | | Attestation ID | `attestation_event_id` | UUID | `identity_corroboration_event` | | Furnishing Entity ID | `furnishing_entity_id` | UUID | `attestation_event` | | Is KYC CIP | `is_kyc_cip` | Boolean | `identity_corroboration_event` | | Identity Corroboration Timestamp | `identity_corroboration_timestamp` | Datetime | `identity_corroboration_event` | | Identity Corroboration Methods | `identity_corroboration_methods` | String | `identity_corroboration_event` | | Identity Corroboration Outcome | `identity_corroboration_outcome` | String | `identity_corroboration_event` | | Total Identity Corroboration Sources Consulted | `total_identity_corroboration_sources_consulted` | Integer | `identity_corroboration_event` | | KYC Decision | `kyc_decision` | String | `identity_corroboration_event` | | Is SSN Match | `is_ssn_match` | Boolean | `identity_corroboration_event` | | Is Name Match | `is_name_match` | Boolean | `identity_corroboration_event` | | Name Verified | `name_verified` | Boolean | `identity_corroboration_event` | | Is DOB Match | `is_dob_match` | Boolean | `identity_corroboration_event` | | SSN Verified | `ssn_verified` | Boolean | `identity_corroboration_event` | | Is Address Match | `is_address_match` | Boolean | `identity_corroboration_event` | | Is Phone Match | `is_phone_match` | Boolean | `identity_corroboration_event` | | Is Email Match | `is_email_match` | Boolean | `identity_corroboration_event` | ### KYCCertificateSnapshot (policy configuration only) These certificate-level fields are used when configuring a [querying policy](/concepts/governance/querying-policies) — e.g. requiring a certificate no older than 30 days. They are filters over issued certificates, not data projected into the query response. | Field | API name | Type | Source model | | - | - | - | - | | Certificate As Of Date | `certificate_as_of_date` | Date | `kyc_certificate_snapshot` | | Certificate Attestation Timestamp | `certificate_attestation_timestamp` | Datetime | `kyc_certificate_snapshot` | | Days Since Certificate As Of Date | `days_since_certificate_as_of_date` | Integer | `kyc_certificate_snapshot` | | Is Address Captured | `is_address_captured` | Boolean | `kyc_certificate_snapshot` | | Is Address Verification Performed | `is_address_verification_performed` | Boolean | `kyc_certificate_snapshot` | | Is Biometric Captured | `is_biometric_captured` | Boolean | `kyc_certificate_snapshot` | | Is Biometric Reviewed | `is_biometric_reviewed` | Boolean | `kyc_certificate_snapshot` | | Is Document Captured | `is_document_captured` | Boolean | `kyc_certificate_snapshot` | | Is Document Reviewed | `is_document_reviewed` | Boolean | `kyc_certificate_snapshot` | | Is Identity Corroboration Performed | `is_identity_corroboration_performed` | Boolean | `kyc_certificate_snapshot` | | Is Liveness Captured | `is_liveness_captured` | Boolean | `kyc_certificate_snapshot` | | Is Liveness Reviewed | `is_liveness_reviewed` | Boolean | `kyc_certificate_snapshot` | ## Related ## In the dashboard ![Run query — choose KYC certificate product](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/05-wizard-step-product.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=207d056bb903592a46c3bdb2c6199266) ![Run query — choose policy](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/06-wizard-step-policy.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=de27cb948e3db91db72d3bea1ed96ca4) ![Run query — choose entity](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/07-wizard-step-entity.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=09fdcfc66dd5fff1a13e762c7845d25d) ![Run query — networks and consent matrix](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/08-wizard-step-networks.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=b04d45e059f3fe51984e4064f68f23cc) ![Query completed — run detail overview](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/09-wizard-success-detail.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=cfccc007fd2f06d5eb61365bdbd2888a) ![Consumer KYC certificate — parsed result fields](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/10-consumer-kyc-result-certificate.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=6a473620d33e883384c3231316d495e0) ![Consumer KYC query — overview tab](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/11-consumer-kyc-result-response.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=0db1a6443b221b6f283966ec291da91a) > **[Querying](/api-overview/querying/overview)** > > Request anatomy, 200 vs 204, billing, and the X-Ref-Id header. > **[Coverage check](/api-overview/querying/coverage-check)** > > Check field coverage before running a billable query. # Network consumption Source: https://docs.solo.one/api-overview/querying/overview How reusing verified work from the network works, and why consent is required **Querying** is how a participant consumes the network: it reads consolidated, already-verified data about an entity instead of re-collecting it. A single query brings together four concepts — a **network**, a **policy**, a **product**, and a **consumer** (or business) — gated by **consent**. ## Why consume network data You query to reuse work, not to "buy data." A query returns a [trust asset](/concepts/trust/trust-assets) — an attested verification with its provenance intact — so the outcome is operational, not informational: * **Faster onboarding** — reuse a verified [KYC](/api-overview/querying/kyc-certificate-snapshot) or [KYB](/api-overview/querying/kyb-certificate-snapshot) certificate instead of repeating document collection and review. * **Less manual review** — start from another institution's completed, evidenced verification rather than from zero. * **Do only the missing work** — a [coverage check](/api-overview/querying/coverage-check) tells you what already exists, so you collect only what's genuinely absent. Read it as *"check whether verified trust assets already exist for this subject,"* not *"fetch a data record."* ## How querying works ```mermaid theme={null} flowchart LR C[Consumer / Business] --> Cons[Consent] Cons --> Q[Product query] N[Networks] --> Q P[Querying policy] --> Q Prod[Product] --> Q Q --> R[Consolidated result] Q -.-> E[Query event
X-Ref-Id] ``` Each piece plays a distinct role: | Concept | Role in the query | | - | - | | **Product** | *What* you're reading (e.g. a KYC certificate). Determines the endpoint and schema. See the [product catalog](/api-overview/querying/products). | | **Network** | *Where* you're reading from. You must be a [querier](/concepts/governance/networks#permissions--roles) of every network you include. | | **Policy** | *Which rules* apply — the field-level [querying policy](/concepts/governance/querying-policies) governing what the query may read and how fresh it must be. | | **Consumer / Business** | *Who* the query is about — resolved from your [consent](/concepts/identity/consent) record. | ## Anatomy of a query Every product query is a `POST` to the product's `/query` endpoint. The request body has two halves: **who** the query is about, and **where + under which rules** to read. | Field | Type | Required | Purpose | | - | - | - | - | | `consent_id` | string | one of¹ | Consent token identifying the subject and your permission to query them. See [Consent](/concepts/identity/consent). | | `consumer_identity_id` / `business_identity_id` | UUID | one of¹ | Direct profile reference for permissible-purpose queries, as an alternative to `consent_id`. | | `network_ids` | UUID\[] | yes | One or more networks to read across. You must hold the querier role in each. | | `policy_id` | UUID | no | The [querying policy](/concepts/governance/querying-policies) applied across every network in `network_ids`. If omitted, each network's default policy is used. | | `furnishing_entity_ids` | UUID\[] | no | Optional furnisher scope. When omitted, data from all furnishers is considered. | ¹ Provide either a `consent_id` *or* a direct profile id — not both. Why both halves matter: * The **subject** half (`consent_id`) carries the legal basis. Querying personal or business data requires a permissible purpose and the subject's permission, and the consent record is the proof. It also resolves the subject's identity so you never repeat their personal details on each call. * The **scope** half (`network_ids` + `policy_id`) tells the network where to read and which field-level rules to apply. The same product behaves differently under different policies, so the pair determines both *what data is considered* and *how fresh and complete it must be*. > [!NOTE] > > Each issued certificate records the exact network + policy pairs it was > resolved under, so the scope you pass is permanently part of the audit trail. ### Full example ```bash theme={null} curl -X POST https://api.solo.one/v1/products/kyc_certificate_snapshot/query \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "consent_id": "a3f0b9c7-…", "policy_id": "5e7d2a14-…", "network_ids": ["9f1c0c2e-…"] }' ``` A successful query returns `200 OK` with the consolidated result and an `X-Ref-Id` response header: ```json theme={null} { "certificate_id": "1c9f4e2a-…", "provenance_event_id": "7b2d9c4e-…", "consumer_identity_id": "c2a4e8d0-…", "result": { "meta": { "network_id": "9f1c0c2e-…", "policy_id": "5e7d2a14-…" }, "document_capture": { "furnishing_entity_id": "e1d2c3b4-…", "attestation_event_id": "f0a1b2c3-…", "assertions": { "document_capture_assertion": true, "document_capture_timestamp": "2026-04-15T00:00:00Z" }, "data": { "document_artifact": "passport_scan.pdf", "document_type": "us_passport", "document_issuing_state": "NY", "document_number": "934712385", "document_issue_date": "2021-03-02", "document_expiration_date": "2031-03-02", "document_capture_method": "mobile_live_capture" } }, "biometric_capture": { "furnishing_entity_id": "e1d2c3b4-…", "attestation_event_id": "f0a1b2c3-…", "assertions": { "biometric_capture_assertion": true, "biometric_capture_timestamp": "2026-04-15T00:00:00Z" }, "data": { "biometric_artifact": "selfie.jpg", "biometric_capture_method": "live_selfie_video" } }, "document_review": null, "biometric_review": null, "liveness_capture": null, "liveness_review": null, "address_capture": null, "address_verification": null, "identity_corroboration": null } } ``` The shape of `result` is product-specific — see the per-product pages for the [KYC certificate](/api-overview/querying/kyc-certificate-snapshot), [KYB certificate](/api-overview/querying/kyb-certificate-snapshot), and [screening lists](/api-overview/querying/screening-lists). ## 200 vs 204 — "here is data" vs "nothing usable" Product query endpoints distinguish two successful outcomes: | Status | Meaning | Body | | - | - | - | | `200 OK` | The query resolved and produced a usable result (e.g. a certificate was issued). | The product's response schema. | | `204 No Content` | The query resolved, but the available data did not satisfy the policy's requirements — no certificate could be created, no usable result exists. | **Empty.** | `204` is not an error. It means the network looked, applied your policy, and found that the furnished data falls short of what the policy demands — for example, a required sub-product was never furnished, or the data is older than the policy's freshness window allows. Clients should treat `204` as "queried, nothing to show" without inspecting product-specific fields. > [!NOTE] > > A `204` response still carries the `X-Ref-Id` header, because the query > itself was resolved and recorded. To avoid paying for predictable `204`s, run > a non-billable [coverage check](/api-overview/querying/coverage-check) first. ## The `X-Ref-Id` header Every product query response — `200` and `204` alike — includes an `X-Ref-Id` response header. Its value is the **query event id**: the durable identifier of this query in the network's audit trail. On `200` responses the same value also appears in the body as `provenance_event_id`. ```text theme={null} HTTP/1.1 200 OK X-Ref-Id: 7b2d9c4e-… Content-Type: application/json ``` Treat it like a receipt: * **Quote it to support.** If a query produced an unexpected result, the `X-Ref-Id` lets SOLO trace the exact request, the policy applied, and the data considered. * **Store it in your own audit log.** It ties your internal decision record to the network's record of the same event. * **Reconcile billing.** Each billable query corresponds to one query event id. ## What you get back — entitlement The fields included in a `200` result are the intersection of: 1. what the network's [querying policy](/concepts/governance/querying-policies) allows, and 2. what you're [entitled](/concepts/governance/entitlement) to read for that entity. Entitlement is earned per participant — typically by having furnished data for the entity or having previously queried it. Two participants running the same query against the same subject can therefore receive different results. If a field you expected is missing, check entitlement before assuming the data was never furnished. See [Entitlement](/concepts/governance/entitlement) for the full model. ## Billing Product query requests are **billable events**. Conservatively: * Each call to a product's `/query` endpoint that resolves — whether it returns `200` or `204` — is recorded as a query event and may be billed. * The [coverage check](/api-overview/querying/coverage-check) (`POST /v1/products/check`) is **not** billable. It exists precisely so you can pre-flight a query without incurring a billable event. * Pricing is defined by your network agreement; this documentation does not state prices. > [!WARNING] > > A `204 No Content` response is still a resolved query. If your integration > retries on `204`, every retry is another billable event. Use the > [coverage check](/api-overview/querying/coverage-check) to avoid querying subjects > whose data cannot satisfy your policy. ## Multi-network queries `network_ids` accepts more than one network, letting a single call read across every network you participate in: ```json theme={null} { "consent_id": "a3f0b9c7-…", "policy_id": "5e7d2a14-…", "network_ids": ["9f1c0c2e-…", "4d8a7f31-…"] } ``` How a multi-network query resolves: * The single `policy_id` is applied uniformly across **every** network listed. If omitted, each network's default policy is used. * Data is gathered from all listed networks; for each sub-product the network selects the oldest matching event from any allowed network. * The result's `meta.network_id` is **anchored to the first network** in `network_ids` — list your primary network first. * You must hold the querier role in every network listed; a network you cannot query fails the request rather than being silently skipped. ## How consent ties in — and why it's needed You cannot query a consumer or business without a valid `consent_id` (or a direct profile id under an established permissible purpose). **1. Gather permission and record consent** Create a consent record for the subject and receive a `consent_id`. See [Consent](/concepts/identity/consent). **2. Reference the consent on each query** Pass the `consent_id` in the query body. The network resolves it to the subject and applies the right field access. **3. Reuse it** The same `consent_id` can be used for repeated queries of that subject while the consent remains valid. > [!WARNING] > > A query without a valid consent for the subject is rejected. Consent is the gate; > [entitlement](/concepts/governance/entitlement) and [policy](/concepts/governance/querying-policies) shape the > result once you're through it. ### Consent vs direct profile queries Product query endpoints accept two identity-resolution paths: * **`consent_id`** — the standard path. The consent token both identifies the subject and proves your permission to query them. Use this for any flow where the subject interacts with you (onboarding, re-verification). * **`consumer_identity_id` / `business_identity_id`** — a direct reference to a profile you can already see, for permissible-purpose queries where consent was established through another channel. The subject must resolve within your scope or the query returns `404`. Both paths produce the same response shape and the same billable query event. When in doubt, use `consent_id`. ## Query failure modes Failures use the standard error envelope — `{"detail": "…", "error_code": "…"}` — described in [Errors](/home/errors). | Status | `error_code` | Typical cause | | - | - | - | | `400 Bad Request` | `VALIDATION_ERROR` | The request is structurally valid but semantically wrong — e.g. a consent that doesn't resolve to a subject. | | `401 Unauthorized` | `AUTHENTICATION_REQUIRED` | Missing, malformed, or expired bearer token. See [Authentication](/home/authentication). | | `403 Forbidden` | `PERMISSION_DENIED` | You're not a querier of a listed network, or your token lacks the required scope. | | `404 Not Found` | `RESOURCE_NOT_FOUND` | The consumer, business, consent, or product could not be resolved. | | `422 Unprocessable Entity` | — | Request body failed schema validation (e.g. `network_ids` empty or missing); `detail` names the field. | | `500 Internal Server Error` | `INTERNAL_ERROR` / `OPERATION_FAILED` | Unexpected server error. Quote the `X-Ref-Id` or `request_id` to support. | Remember that `204 No Content` is **not** in this table — it's a success status meaning the policy's requirements were not met by the available data. ## Integration checklist A robust querying integration usually looks like this: 1. **Record consent once, reuse it.** One consent record covers repeated queries of the same subject while it remains valid. 2. **Pre-flight with the coverage check** when a `204` would hurt your UX or your budget — it's free and uses the same scope fields as the query. 3. **Handle `204` as a first-class outcome**, not an error: branch your product flow on "no usable result" rather than retrying. 4. **Persist `provenance_event_id` / `X-Ref-Id`** alongside your own decision records for audit, support, and billing reconciliation. 5. **Treat missing fields as an entitlement question first** — check what you've furnished and what the policy exposes before raising a data issue. ## The network control plane Reuse is safe because it is governed by independent controls — the network's control plane. Every one of these exists today; none can be bypassed by a single actor: | Control | What it governs | Where it's defined | | - | - | - | | **Consent** | Whether a subject's data may be read at all, its scope, and its expiry | [Consent](/concepts/identity/consent) (`scope`, `expires_at`, `consented_fields`) | | **Entitlement** | Which fields a given reader may see, earned by participation | [Entitlement](/concepts/governance/entitlement) | | **Querying policy** | What a product exposes, and how fresh it must be to count | [Querying policies](/concepts/governance/querying-policies) (freshness windows) | | **`204` on insufficiency** | Refusal to return stale or incomplete results | This page — `204 No Content` | A governor controls policy but not consent; a subject controls consent but not entitlement; your own history determines entitlement but not policy. That separation is what lets institutions reuse each other's work without anyone losing control of their own. ## In the dashboard ![Run query — choose consumer or business](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/04-wizard-step-kind.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=e626158a43f65f7522ecb5891e4e8464) ![Run query — choose KYC certificate product](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/05-wizard-step-product.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=207d056bb903592a46c3bdb2c6199266) ![Run query — choose policy](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/06-wizard-step-policy.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=de27cb948e3db91db72d3bea1ed96ca4) ![Run query — choose entity](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/07-wizard-step-entity.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=09fdcfc66dd5fff1a13e762c7845d25d) ![Run query — networks and consent matrix](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/08-wizard-step-networks.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=b04d45e059f3fe51984e4064f68f23cc) ![Query completed — run detail overview](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/09-wizard-success-detail.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=cfccc007fd2f06d5eb61365bdbd2888a) ![Consumer KYC query — overview tab](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/11-consumer-kyc-result-response.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=0db1a6443b221b6f283966ec291da91a) ![Query history list — recent consumer query](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/12-history-list.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=c101829f84aa1c4d3ef5f11cc1275e04) ![Query history — consumer KYC run detail opened from list](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/13-history-run-detail-from-list.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=a9c193f67ada0d6895325902ff6b560c) ![Business KYB query — REST response payload](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/24-business-kyb-result-response.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=53ede6a63d002c413dc1d5cfb726ed60) ![Consumer — query history tab](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/entities/policies-and-entities-complete/23-consumer-tab-queries.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=d0123d8c7dca23f8cc8333f7bf0329d9) ## Related concepts > **[Products](/api-overview/querying/products)** > > The catalog of what you can query. > **[Coverage check](/api-overview/querying/coverage-check)** > > Pre-flight a query without a billable event. > **[Furnishing](/api-overview/furnishing/overview)** > > The other half of the network — contributing data. > **[Query a product](/home/quickstart/query-a-product)** > > A hands-on walkthrough. # Products Source: https://docs.solo.one/api-overview/querying/products The standardized data sets you query and furnish on the network A **product** is a standardized, named data set that you can **query** (read) and — for certificate products — **furnish** (write) through the network. Products give every participant a common schema for a given verification domain, so data furnished by one participant can be queried by another without bespoke integration. ## The catalog Four products are queryable today. The exact set available to you depends on the networks you belong to. | Product | What it answers | Subject | Query path | | - | - | - | - | | [KYC Certificate](/api-overview/querying/kyc-certificate-snapshot) | "Has this consumer's identity been verified — documents, biometrics, liveness, address, and corroboration?" | Consumer | `POST /v1/products/kyc_certificate_snapshot/query` | | [KYB Certificate](/api-overview/querying/kyb-certificate-snapshot) | "Has this business been verified — identity, ownership & control, and risk/compliance?" | Business | `POST /v1/products/kyb_certificate_snapshot/query` | | [Bank-Specific Bad Actor List](/api-overview/querying/screening-lists) | "Has this consumer violated a documented policy of this sponsor bank's network?" | Consumer | `POST /v1/products/bank_specific_bad_actor_list/query` | | [Cross-Bank Financial Crimes Watch List](/api-overview/querying/screening-lists) | "Is this consumer associated with suspicious financial-crimes activity reported across participating banks?" | Consumer | `POST /v1/products/cross_bank_financial_crimes_watch_list/query` | They fall into two families: * **Certificates** are consolidated, multi-attribute verification results assembled from data furnished by network participants. Querying one can *issue* a reusable certificate for the subject. Certificate queries return `204 No Content` when the available data cannot satisfy the policy. * **Screening lists** are read-only yes/no checks against lists of flagged individuals. They return a listing indicator plus a small set of context fields, never a certificate — and always `200 OK`, with a clean result expressed as `"is_listed": false`. | Behavior | Certificates | Screening lists | | - | - | - | | Furnishable via API | Yes — `/furnish` | No (query-only) | | Issues an artifact | Yes — `certificate_id` | No | | Empty result | `204 No Content` | `200` with `is_listed: false` | | Billable query event | Yes | Yes | ## The query / furnish pattern > **Query** > > **Read** consolidated data for an entity, drawn from what authorized > participants have furnished. Requires a [consent](/concepts/identity/consent) ID > or a direct profile reference. > **Furnish** > > **Contribute** verified data for an entity into a network, making it available > for future queries by entitled participants. ```text theme={null} POST /v1/products/{product}/query POST /v1/products/{product}/furnish (certificate products) ``` Certificate products accept structured JSON furnishing via the API, and bulk furnishing via [file upload](/api-overview/furnishing/file-upload) or [SFTP](/api-overview/sftp/overview). The two screening lists are query-only over the API; the data behind them is contributed through governed furnishing flows. There is also one utility endpoint that is **not** a product query: `POST /v1/products/check`, the non-billable [coverage check](/api-overview/querying/coverage-check), which reports whether the furnished data for a subject can satisfy a policy *before* you run a billable query. ## How products relate to networks and policies ```mermaid theme={null} flowchart LR Prod[Product
schema] --> NP[Offered in a network] Net[Network] --> NP NP --> Pol[Querying policy
field rules + freshness] Pol --> Q[Query result] ``` A product defines *what* data exists — its models, fields, and types. A [network](/concepts/governance/networks) decides *which* products it offers to its members. A [querying policy](/concepts/governance/querying-policies) defines, per network, *which parts* of a product a query may read and under what conditions (e.g. how recent a verification must be). The same product can behave differently in two networks because each network attaches its own policies. This is why every query carries both `network_ids` and a `policy_id`: the product determines the endpoint and response schema, while the network + policy scope determines what actually comes back. What you're [entitled](/concepts/governance/entitlement) to read narrows it further. Each product's schema is defined as a set of **models** — named groups of typed fields such as `DocumentCaptureEvent` or `BusinessRiskComplianceEvent`. These model and field names are the shared vocabulary across the platform: policies select them, the [coverage check](/api-overview/querying/coverage-check) reports against them, and the per-product field reference tables document them. When a policy "requires liveness within 30 days," it is expressing a filter over a product model's fields — the same fields you'll see in the query response. ## Querying a product A product query reads consolidated data for one entity. It needs a **consent ID** (or direct profile id) identifying the subject, and a **network + policy** scope: ```json theme={null} { "consent_id": "a3f0b9c7-…", "policy_id": "5e7d2a14-…", "network_ids": ["9f1c0c2e-…"] } ``` See [Querying](/api-overview/querying/overview) for the full request anatomy, `200` vs `204` semantics, the `X-Ref-Id` audit header, and billing notes. ## Furnishing a product Furnishing contributes one or more records for an entity into a network: ```json theme={null} { "network_id": "9f1c0c2e-…", "application_date": "2026-05-28", "records": [ { "first_name": "Jane", "last_name": "Doe", "date_of_birth": "1990-01-15", "social_security_number": "123-45-6789" } ] } ``` The network matches each record to an entity and stores the furnished data under your organization, building your [entitlement](/concepts/governance/entitlement) to query it back later. See [Furnishing](/api-overview/furnishing/overview). ## Choosing a product | If you need to… | Use | | - | - | | Verify a consumer's identity at onboarding | [KYC Certificate](/api-overview/querying/kyc-certificate-snapshot) | | Verify a business and its beneficial owners | [KYB Certificate](/api-overview/querying/kyb-certificate-snapshot) | | Screen a consumer against your sponsor bank's network history | [Bank-Specific Bad Actor List](/api-overview/querying/screening-lists) | | Screen a consumer for cross-bank financial-crimes signals | [Cross-Bank Financial Crimes Watch List](/api-overview/querying/screening-lists) | | Know whether a query can succeed before paying for it | [Coverage Check](/api-overview/querying/coverage-check) | In practice these compose: an onboarding flow might run both screening lists first, then a coverage check, then the KYC certificate query — three billable list/certificate queries at most, with the free check deciding whether the certificate query is worth running. ## In the dashboard ![Products catalogue — By Templates](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/02-products-catalogue-templates.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=9b7e2e0f75d8afdc2b2bcea42ec425b6) ![KYC product detail — overview](https://mintcdn.com/soloone/9HdwBb7FHoPnTmO1/images/dashboard/journeys/furnishing/furnishing-complete/02-product-detail-overview.png?fit=max\&auto=format\&n=9HdwBb7FHoPnTmO1\&q=85\&s=81135c5c74900fa90dd0559553e3749e) ![Products catalogue — By Attributes tab](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/03-products-catalogue-attributes.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=1fc84989796d721558c555e7fc8aab9c) ![Run query — choose KYC certificate product](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/05-wizard-step-product.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=207d056bb903592a46c3bdb2c6199266) ![KYC product detail — Querying Policies tab](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/16-product-detail-querying-policies.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=ccaea0c9cc24e25461fdf8e0b9f1a963) ![Run query opened from product detail](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/18-from-product-detail.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=ca7cbc708190bd67372c5f50aab6c6e4) ![Run query — choose KYB certificate product](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/19-wizard-step-kyb-product.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=3e27128dc13fa314b54e4e8266264acd) ## Product deep dives > **[KYC Certificate](/api-overview/querying/kyc-certificate-snapshot)** > > Consolidated consumer identity verification — nine sub-products, full field > reference. > **[KYB Certificate](/api-overview/querying/kyb-certificate-snapshot)** > > Business identity, ownership & control, and risk/compliance in one > certificate. > **[Screening Lists](/api-overview/querying/screening-lists)** > > Bank-specific bad actor and cross-bank financial crimes watch lists. > **[Coverage Check](/api-overview/querying/coverage-check)** > > Non-billable pre-flight: can this query succeed under this policy? # Screening Lists Source: https://docs.solo.one/api-overview/querying/screening-lists Bank-specific bad actor and cross-bank financial crimes watch lists SOLO offers two **screening list** products in the Fraud & Financial Crime category. Both answer a narrow question — *is this consumer on a list?* — and return a listing indicator plus a small set of context fields. Unlike [certificates](/api-overview/querying/kyc-certificate-snapshot), screening list queries never issue a reusable artifact; they are point-in-time checks. | | Bank-Specific Bad Actor List | Cross-Bank Financial Crimes Watch List | | - | - | - | | **What it is** | A sponsor-bank-scoped list of specific individuals who, as prior customers of one or more of the bank's fintech partners, violated one or more specific documented bank policies. | A cross-bank shared consortium that extends beyond one sponsor-bank network and focuses on sharing suspicious financial-crimes activity. Governed by banks as defined by 31 CFR 1020.100(d) under 314(b) permissible purpose. | | **Scope** | Your sponsor bank's network only — results never cross network boundaries. | All participating banks — visibility spans the consortium. | | **Use case** | Bad Actor Screening — catching repeat offenders re-applying within the same bank's networks. | Watch List Screening — surfacing inter-bank financial-crimes signals. | | **Subject** | Consumer | Consumer | | **Query path** | `POST /v1/products/bank_specific_bad_actor_list/query` | `POST /v1/products/cross_bank_financial_crimes_watch_list/query` | The key difference is **who can see what**. The bad actor list is scoped to one sponsor bank: a consumer flagged in Bank A's network is invisible to Bank B. The watch list is the opposite by design — it exists so that suspicious financial-crimes activity observed at one bank is visible to the others, under the 314(b) information-sharing framework. Both lists are **query-only** over the API. The events behind them are contributed by network participants through governed furnishing flows. ## Querying Both endpoints take the standard [query request](/api-overview/querying/overview): a `consent_id` (or `consumer_identity_id` for permissible-purpose lookups) plus the `network_ids` scope, with optional `policy_id` and `furnishing_entity_ids`. ### Bank-Specific Bad Actor List ```bash theme={null} curl -X POST https://api.solo.one/v1/products/bank_specific_bad_actor_list/query \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "consent_id": "a3f0b9c7-…", "network_ids": ["9f1c0c2e-…"] }' ``` ```json theme={null} { "provenance_event_id": "7b2d9c4e-…", "consumer_identity_id": "c2a4e8d0-…", "furnishing_entity_id": null, "is_listed": false, "bad_actor_list_placement_date": null, "bad_actor_list_reason_code": null } ``` | Response field | Type | Meaning | | - | - | - | | `provenance_event_id` | UUID | The billable provenance event id — also in the `X-Ref-Id` header. | | `consumer_identity_id` | UUID | The resolved subject. | | `furnishing_entity_id` | UUID, nullable | The participant whose listing backed the result, when listed. | | `is_listed` | boolean | Whether the consumer appears on the list. | | `bad_actor_list_placement_date` | date, nullable | When the consumer was placed on the list. | | `bad_actor_list_reason_code` | string, nullable | The documented policy-violation reason (e.g. `account_abuse`, `fraud`, `policy_violation`). | When the consumer is listed, the context fields are populated: ```json theme={null} { "provenance_event_id": "7b2d9c4e-…", "consumer_identity_id": "c2a4e8d0-…", "furnishing_entity_id": "e1d2c3b4-…", "is_listed": true, "bad_actor_list_placement_date": "2025-11-04", "bad_actor_list_reason_code": "account_abuse" } ``` ### Cross-Bank Financial Crimes Watch List ```bash theme={null} curl -X POST https://api.solo.one/v1/products/cross_bank_financial_crimes_watch_list/query \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "consent_id": "a3f0b9c7-…", "network_ids": ["9f1c0c2e-…"] }' ``` ```json theme={null} { "provenance_event_id": "7b2d9c4e-…", "consumer_identity_id": "c2a4e8d0-…", "furnishing_entity_id": null, "is_listed": false, "entity_level_adverse_action_eligible_indicator": null, "watch_list_placement_date": null } ``` | Response field | Type | Meaning | | - | - | - | | `provenance_event_id` | UUID | The billable provenance event id — also in the `X-Ref-Id` header. | | `consumer_identity_id` | UUID | The resolved subject. | | `furnishing_entity_id` | UUID, nullable | The participant whose listing backed the result, when listed. | | `is_listed` | boolean | Whether the consumer appears on the watch list. | | `entity_level_adverse_action_eligible_indicator` | boolean, nullable | Whether the signal is eligible to support adverse action at the entity level. | | `watch_list_placement_date` | date, nullable | When the consumer was placed on the watch list. | > [!NOTE] > > Screening list queries always return `200 OK` — a clean result is expressed as > `"is_listed": false`, not as a `204`. (The `204` semantics on the > [certificate endpoints](/api-overview/querying/kyc-certificate-snapshot#when-you-get-a-204) > signal that *no certificate could be created*; a list check always produces a > usable answer.) Both queries are billable events and carry the > [`X-Ref-Id` header](/api-overview/querying/overview#the-x-ref-id-header). ## How matching works Subjects are matched against the lists using the consumer identity behind your [consent](/concepts/identity/consent) record. Each list defines its match inputs — SSN and date of birth are mandatory; name, phone, and email refine the match: | Match input | API name | Type | Required | | - | - | - | - | | Social Security Number | `social_security_number` | String | Yes | | Date of Birth | `date_of_birth` | Date | Yes | | First Name | `first_name` | String | No | | Last Name | `last_name` | String | No | | Phone Number | `phone_number` | String | No | | Email | `personal_email` | String | No | These inputs are identical for both lists. ## Field reference The list events behind each product, as defined in the product schema. *Field* is the display name, *API name* is the `field_name` used in policies, and *Source model* is the underlying table. ### Bank-Specific Bad Actor List — BadActorEvent | Field | API name | Type | Source model | | - | - | - | - | | Bad Actor Reason Code | `bad_actor_reason_code` | String | `bad_actor_event` | | Bad Actor Reason Definition | `bad_actor_reason_definition` | String | `bad_actor_event` | | Bad Actor List Placement Date | `bad_actor_list_placement_date` | Date | `bad_actor_event` | Reason codes are drawn from a documented set — `account_abuse`, `fraud`, and `policy_violation` — and each listing carries the bank's definition of the violated policy in `bad_actor_reason_definition`. ### Cross-Bank Financial Crimes Watch List — FinancialCrimesWatchEvent | Field | API name | Type | Source model | | - | - | - | - | | Event Date | `event_date` | Date | `financial_crimes_watch_event` | | Event Category | `event_category` | String | `financial_crimes_watch_event` | | Signal Level | `signal_level` | String | `financial_crimes_watch_event` | Event categories include `money_laundering`, `terrorist_financing`, and `fraud`; signal levels are `high`, `medium`, or `low`. A network's [querying policy](/concepts/governance/querying-policies) can restrict which categories and signal levels a query considers — for example, only `high`-signal events. ## Compliance posture > [!WARNING] > > Watch-list signals are screening inputs, not adjudications. Whether a listing > can support adverse action depends on your program's compliance framework — > the cross-bank list operates under 314(b) permissible purpose, and the > `entity_level_adverse_action_eligible_indicator` field exists precisely > because not every signal qualifies. Route hits to your compliance review > process. ## Related > **[Querying](/api-overview/querying/overview)** > > Request anatomy, billing, and the X-Ref-Id header. > **[Products](/api-overview/querying/products)** > > The full product catalog. # SFTP Getting Started Source: https://docs.solo.one/api-overview/sftp/getting-started Send your first JSONL file to the SOLO Network over SFTP This guide walks you through your first SFTP upload end-to-end — from getting credentials to confirming that the network ingested your data. Plan on about 15 minutes. ## What you'll need **1. A WorkOS API key for your organization** SFTP authentication is delegated to WorkOS. Your SOLO account manager will help you (or your WorkOS admin) mint a key under your organization. The key string itself is the SFTP password. > [!WARNING] > > Treat the API key like any other credential. Store it in a secrets manager and never commit it to source control. The key — not the username — is what grants access: anyone holding it can upload as your organization. **2. The email you use to sign in to SOLO** Your email is the SFTP username. It identifies *who* is connecting and selects the environment via an optional plus-tag. The organization your uploads are attributed to is determined by the **API key's owning organization**, not by the email — so a valid key is required no matter what username you present. **3. An SFTP client** Anything that speaks SFTP works — the `sftp` command from OpenSSH, [FileZilla](https://filezilla-project.org/), [Cyberduck](https://cyberduck.io/), or a library such as Python's [Paramiko](https://www.paramiko.org/). ## Step 1 — Pick an environment We recommend doing your first upload against **sandbox**. Sandbox is a fully isolated copy of the network: nothing you upload there reaches production data, so it's the right place to learn the format and verify your pipeline. You select an environment by appending a plus-tag to the local part of your email: | Environment | Username Format | Example | | - | - | - | | Sandbox | `email+sandbox@domain.com` | `alice+sandbox@yourbank.com` | | Production | `email@domain.com` | `alice@yourbank.com` | Each environment writes to its own storage bucket and database; there is no crossover. Usernames are case-insensitive — `Alice@YourBank.com` and `alice@yourbank.com` are the same user. ## Step 2 — Connect The SFTP host is the same for every environment: ``` sftp.solo.one:22 ``` ```bash sftp (CLI) theme={null} sftp alice+sandbox@yourbank.com@sftp.solo.one # When prompted, paste your WorkOS API key as the password. ``` ```python Python (paramiko) theme={null} import paramiko transport = paramiko.Transport(("sftp.solo.one", 22)) transport.connect( username="alice+sandbox@yourbank.com", password="sk_live_your_workos_api_key", ) sftp = paramiko.SFTPClient.from_transport(transport) # ... upload here ... sftp.close() transport.close() ``` ```text ~/.ssh/config theme={null} Host solo-sftp-sandbox HostName sftp.solo.one User alice+sandbox@yourbank.com Port 22 ``` On a successful connection you'll land at the root of your organization's upload area. > [!WARNING] > > Your SFTP session is **upload-only**: you can `put` files and create directories, but `ls`, `get`, overwriting, and deleting are not permitted. Don't be surprised when a directory listing is denied — that's policy, not a fault. Verification happens in the dashboard (Step 5), not over SFTP. > [!TIP] > > If the server rejects you immediately, the most common causes are (1) the API key has been revoked, (2) your organization isn't provisioned in SOLO yet, or (3) the plus-tag is misspelled (only `+sandbox`, `+prod`, or no tag are accepted). Re-check the username and key, then reach out to your account manager if it persists. ## Step 3 — Write a JSONL file Every upload is a [JSONL file](/api-overview/furnishing/file-upload): UTF-8 text with one JSON object per line. Each record carries a `_type` naming the kind of record it is — that's how the network decides which mappings receive it. Find the record type in the dashboard: open **Feeds**, pick the feed you're delivering, and read its sample record. The sample's `_type` is the value to use, and its other fields are the ones the feed's mapping reads. For this walkthrough, write one consumer record for a feed whose record type is `consumers`: ```json first_upload.jsonl theme={null} {"_type":"consumers","ssn":"123456789","dob":"1990-01-15","first_name":"Jane","last_name":"Doe"} ``` A few things to know about the format ([full reference](/api-overview/furnishing/file-upload#file-format)): * **One compact object per line** — not a JSON array, and not pretty-printed across lines. * **Blank lines are skipped.** Every other line is processed independently, so a single bad line won't fail the rest of the file. * **Identifiers are strings.** Write SSNs and tax identifiers as `"012345678"`, never as numbers — a number drops the leading zero. * **Filenames** use letters, digits, `_`, and `-`, with a single `.jsonl` extension. ## Step 4 — Upload Drop the file into `network_{network_id}/jsonl_furnish_data/`, where `{network_id}` is the network the records go to. The feed's detail page in the dashboard shows the network id in its drop path. Your session is already scoped to your organization, so leave off the drop path's leading `entity_…/` segment: ```bash sftp (CLI) theme={null} sftp> mkdir network_9f1c0c2e-7a44-4f1e-9d2a-08b6f3a1c5d7 sftp> cd network_9f1c0c2e-7a44-4f1e-9d2a-08b6f3a1c5d7 sftp> mkdir jsonl_furnish_data sftp> cd jsonl_furnish_data sftp> put first_upload.jsonl sftp> bye ``` ```python Python (paramiko) theme={null} network_dir = "network_9f1c0c2e-7a44-4f1e-9d2a-08b6f3a1c5d7" for directory in (network_dir, f"{network_dir}/jsonl_furnish_data"): try: sftp.mkdir(directory) except OSError: pass # already exists sftp.put("first_upload.jsonl", f"/{network_dir}/jsonl_furnish_data/first_upload.jsonl", confirm=False) ``` > [!TIP] > > Pick a fresh filename for every upload (a date or batch number works well). Sessions can't overwrite or delete existing files, so re-using a name from a previous drop will be rejected. Pass `confirm=False` in Paramiko — its post-upload check lists the file, which upload-only sessions don't allow. ## Step 5 — Verify ingestion Ingestion is event-driven and asynchronous — processing starts within seconds of the file landing, and a small file typically completes in well under a minute. The cleanest way to check is the [SOLO dashboard](https://app.solo.one): the **Uploads** page lists every file your organization has sent, with the records read and the succeeded, filtered, failed, and unmatched counts. The feed's detail page shows its recent deliveries and names any record types no mapping claimed. > [!NOTE] > > Re-sending data is safe. Identity records resolve to the existing consumer or business on their natural keys (SSN and date of birth for consumers; tax identifier and jurisdiction for businesses), so a corrected re-upload attaches to the same profiles instead of creating duplicates. ## Common first-upload issues > **Authentication failed / Permission denied at login** > > Confirm the username is your full email address (with `+sandbox` for sandbox), and the password is the literal WorkOS API key string — no `Bearer` prefix, no quotes. If you rotated the key recently, the old value is rejected; a freshly revoked key may take up to a minute to be refused everywhere, since auth results are briefly cached. > **"Permission denied" on ls, get, or overwrite** > > Working as intended — SFTP access is upload-only. You can `put` new files and create directories; everything else is denied. Use the dashboard to inspect what you've uploaded, and use a new filename rather than overwriting an old one. > **"Host key verification failed"** > > The first time you connect from a new machine your SSH client doesn't know the server's host key yet. Either accept the fingerprint at the prompt, or pre-populate your `known_hosts`: > > ```bash theme={null} > ssh-keyscan sftp.solo.one >> ~/.ssh/known_hosts > ``` > **File uploaded but no upload appears in the dashboard** > > The file landed outside `jsonl_furnish_data/`, so it was stored but never ingested. Re-upload it into the feed's drop path. > **Records are counted as unmatched** > > No active mapping in the target network claims their `_type`. Compare the value with the feed's sample record, confirm the feed's mapping is active rather than a draft, and confirm the drop path names the network you expected. > **The upload fails with an access error** > > Your organization doesn't hold the furnisher or governor role in the network named by the drop path. Check the network id in the path, or ask the network's governor to add your organization as a furnisher. ## Where to go next > **[SFTP Overview](/api-overview/sftp/overview)** > > The full mental model — directories, environments, how routing works. > **[JSONL Files](/api-overview/furnishing/file-upload)** > > The file format, record types, and results reference. > **[Furnishing Policies](/concepts/governance/furnishing-policies)** > > How mappings turn records into network data. > **[API Reference](/api-reference/introduction)** > > Prefer to furnish over HTTPS? Records can be sent one at a time as REST calls. # SFTP Overview Source: https://docs.solo.one/api-overview/sftp/overview Bulk data ingestion via SFTP file uploads The SOLO Network supports bulk data ingestion via SFTP. Drop [JSONL files](/api-overview/furnishing/file-upload) into your organization's SFTP endpoint, and each file is automatically ingested record by record into the network — through the **same furnishing pipeline** as a [dashboard upload](/api-overview/furnishing/file-upload#uploading-from-the-dashboard). SFTP is a transport, not a separate system: a record dropped over SFTP and the same record uploaded from the dashboard produce identical network data. Choose SFTP when your data already leaves your systems as files on a schedule. Core-banking platforms and data warehouses can usually target an SFTP endpoint with no custom code, which makes this the natural channel for nightly or monthly batch drops. ## Connecting Connect to `sftp.solo.one` on port 22. Your **email address** is the username and your organization's **WorkOS API key** is the password: ```bash theme={null} sftp your-email@yourbank.com@sftp.solo.one ``` For the sandbox environment, append `+sandbox` to the local part of your email: ```bash theme={null} sftp your-email+sandbox@yourbank.com@sftp.solo.one ``` Or using an SFTP library: ```python Python (paramiko) theme={null} import paramiko transport = paramiko.Transport(("sftp.solo.one", 22)) transport.connect( username="alice@yourbank.com", password="sk_live_your_workos_api_key", ) sftp = paramiko.SFTPClient.from_transport(transport) sftp.put("consumers_aug.jsonl", "/jsonl_furnish_data/consumers_aug.jsonl") sftp.close() transport.close() ``` ```bash SSH config theme={null} # ~/.ssh/config Host solo-sftp HostName sftp.solo.one User alice@yourbank.com Port 22 ``` ### How authentication works Every connection is verified live, not against a stored password: 1. The username is parsed into an email and an environment tag (`+sandbox` → sandbox; no tag → production). Usernames are case-insensitive. An unrecognized tag is rejected outright. 2. The password is validated as a **WorkOS API key** against WorkOS. Revoked or malformed keys are rejected. 3. The key's owning WorkOS organization — not anything in the username — is resolved to your SOLO organization. Your session is scoped to that organization's isolated storage area in the selected environment. > [!NOTE] > > Because authorization derives from the **API key**, the key is the credential > that matters: rotating or revoking it in WorkOS immediately cuts off SFTP > access (allow up to a minute for cached sessions to expire). Contact your > SOLO account manager to provision keys. > [!WARNING] > > SFTP sessions are **upload-only**. You can `put` files and create > directories, but listing, downloading, overwriting, and deleting are not > permitted. Upload each file under a name you haven't used before, and use > the SOLO dashboard — not the SFTP session — to confirm what was ingested. ## Directory layout Files go into the `jsonl_furnish_data/` directory, optionally under a network directory that names the network to furnish into: ``` network_{network_id}/jsonl_furnish_data/{filename}.jsonl jsonl_furnish_data/{filename}.jsonl ``` * **`network_{network_id}/jsonl_furnish_data/`** furnishes into that network. Each SFTP feed in the dashboard shows the network id in its drop path; leave off the path's leading `entity_…/` segment, since your session is already scoped to your organization. * **`jsonl_furnish_data/`** on its own furnishes into your organization's governed network. Files anywhere else are stored but never ingested. ## What happens on upload Ingestion is event-driven — there is no polling window to wait for: ```mermaid theme={null} flowchart LR U[You: sftp put] --> S[(Org-scoped storage)] S -->|object-created event| W[JSONL ingestion workflow] W --> P[Read record by record] P --> R[Route each record by _type] R --> F[Mappings: map, match, persist] F --> D[(Network data)] W --> E[Upload recorded with per-record results] ``` 1. **Upload.** The file lands in your organization's isolated storage area, prefixed by environment and organization. 2. **Trigger.** The storage event fires the ingestion workflow within seconds of the upload completing. 3. **Route.** Each line is read as one record and sent to every active mapping in the target network that claims its [`_type`](/api-overview/furnishing/file-upload#record-types). 4. **Ingest.** Each mapping maps the record, matches it to a consumer or business, and persists its events. 5. **Record.** The outcome — records read, successes, filtered, failures, and unmatched record types — is recorded on the upload, which you can review in the dashboard. Key behaviors: * **Record-level processing** — each line succeeds or fails independently. A single invalid line never sinks the rest of the file. * **Unmatched is not failed** — records whose `_type` no active mapping claims are skipped deliberately and reported as unmatched. * **Safe re-runs** — identity records resolve to the existing consumer or business on their natural keys, so re-uploading a corrected batch attaches to the same profiles instead of duplicating them. ## Relationship to the other channels SFTP shares everything but the transport with the dashboard upload: * The same [JSONL format](/api-overview/furnishing/file-upload#file-format) and the same routing by `_type`. * The dashboard shows SFTP drops and dashboard uploads in the same Uploads view, with the same per-record result reporting. * Furnished records earn the same [entitlement](/concepts/governance/entitlement) as a per-record `POST /v1/products/kyc_certificate_snapshot/furnish` call. That means you can mix channels freely: backfill history over SFTP, furnish new onboarding events over REST in real time, and the network treats the resulting data identically. ## Environments Use plus-addressing on the email username to target an environment: | Environment | Username format | Example | | - | - | - | | Production | `email@domain.com` | `alice@yourbank.com` | | Sandbox | `email+sandbox@domain.com` | `alice+sandbox@yourbank.com` | Each environment writes to its own isolated storage; nothing uploaded to sandbox can reach production data. Start in sandbox — the [getting-started guide](/api-overview/sftp/getting-started) walks through a first upload end to end. ## Who can upload Your organization must hold the **furnisher** or **governor** [role](/concepts/governance/network-roles) in the target network. A file aimed at a network you don't furnish into still uploads, but its ingestion fails with an access error and no records are written. ## In the dashboard ![Furnishing channels — REST API, SFTP file drops, and data lake connectors](https://mintcdn.com/soloone/9HdwBb7FHoPnTmO1/images/dashboard/journeys/sources/furnishing-complete/01-list.png?fit=max\&auto=format\&n=9HdwBb7FHoPnTmO1\&q=85\&s=d05aca00353a37c0b3dfa7b59185e06a) > **[Getting Started](/api-overview/sftp/getting-started)** > > Your first upload, end to end, in about 15 minutes. > **[JSONL Files](/api-overview/furnishing/file-upload)** > > The file format, record types, and results. > **[Furnishing Policies](/concepts/governance/furnishing-policies)** > > How mappings turn records into network data. > **[Furnishing Overview](/api-overview/furnishing/overview)** > > How SFTP relates to the REST furnishing channel. # Query the bank-specific bad actor list for a consumer or business Source: https://docs.solo.one/api-reference/bank-specific-bad-actor-list/query POST /v1/products/bank_specific_bad_actor_list/query # Create a business consent record (direct, server-to-server). Returns a consent_id used for all subsequent queries. Source: https://docs.solo.one/api-reference/business-identity/business_identity POST /v1/consent/business_identity Create a business consent record (direct, server-to-server). Returns a consent_id used for all subsequent queries. # Create a consumer consent record (direct, server-to-server). Returns a consent_id used for all subsequent queries. Source: https://docs.solo.one/api-reference/consumer-identity/consumer_identity POST /v1/consent/consumer_identity Create a consumer consent record (direct, server-to-server). Returns a consent_id used for all subsequent queries. # Query the cross-bank financial crimes watch list for a consumer or business Source: https://docs.solo.one/api-reference/cross-bank-financial-crimes-watch-list/query POST /v1/products/cross_bank_financial_crimes_watch_list/query # Search business core identities by name, EIN, or email. Source: https://docs.solo.one/api-reference/entities/search GET /v1/entities/business_identity/search Search business core identities using partial or exact field matches. Parameters ---- `network_id` : `UUID`. Network scope for the search. `business_legal_name` : `str`, optional. Case-insensitive partial match on legal name. `business_dba_name` : `str`, optional. Case-insensitive partial match on DBA name. `business_email` : `str`, optional. Case-insensitive partial match on email. `federal_ein` : `str`, optional. Exact match on federal EIN. `limit` : `int`. Maximum number of results (1-100). Defaults to 20. Returns ---- `results` : `list[BusinessIdentitySearchResult]`. Matching business core identities. Examples ----- ``` GET /entities/business_identity/search?network_id=...&business_legal_name=Acme&limit=10 ``` # List furnishing policies in a network. Source: https://docs.solo.one/api-reference/furnishing-policies/furnishing GET /v1/networks/policies/furnishing List furnishing policies visible within a network scope. Results are paginated with ``limit`` (1-100, default 20) and ``offset`` (>=0, default 0). Parameters ---- `network_id` : `UUID`. Network scope for the listing. `limit` : `int`. Maximum number of results. Defaults to 20. `offset` : `int`. Number of results to skip. Defaults to 0. Returns ---- `results` : `list[MappingResult]`. Page of matching furnishing policies. Examples ----- ``` GET /networks/policies/furnishing?network_id=...&limit=10 ``` # Get a furnishing policy by id. Source: https://docs.solo.one/api-reference/furnishing-policies/{policy_id} GET /v1/networks/policies/furnishing/{policy_id} Fetch a single furnishing policy by id, scoped to a network. Parameters ---- `policy_id` : `UUID`. Identifier of the furnishing policy to fetch. `network_id` : `UUID`. Network scope for the lookup. Returns ---- `result` : `MappingResult`. The matching furnishing policy. Raises ---- `NotFoundError`. If no furnishing policy with the given id is visible within the given network scope. Examples ----- ``` GET /networks/policies/furnishing/{policy_id}?network_id=... ``` # AI-suggest a mapping/1 document for one entry — verified, but required fields the source cannot provide stay unmapped (reported via omissions) rather than fabricated. Source: https://docs.solo.one/api-reference/furnishing-—-mapping-dry-run/suggest POST /v1/furnishing/mapping/suggest # Dry-run trace a mapping over one sample input line — the value at every AST path. Source: https://docs.solo.one/api-reference/furnishing-—-mapping-dry-run/trace POST /v1/furnishing/mapping/trace # Describe every model in the data graph. Source: https://docs.solo.one/api-reference/graph/models GET /v1/graph/models Describe the data graph: each model, its resource path, and its fields. Products and policies are both projections of this graph, so this is the ground truth either one is defined against. Call it to discover which resources exist and which relationships can be expanded. Returns ---- `models` : `GraphModelsResponse`. One entry per model the graph serves. Examples ----- ``` GET /v1/graph/models ``` # Read one graph object by id. Source: https://docs.solo.one/api-reference/graph/{object_id} GET /v1/graph/{resource}/{object_id} Read a single graph object by its global id. Models the graph exposes only through a relationship have no collection endpoint, but are still readable here and expandable from their parent. Parameters ---- `resource` : `str`. Resource path segment, as reported by ``GET /v1/graph/models``. `object_id` : `str`. The object's global id. `network_id` : `list[UUID]`. Networks to read within. Repeat the parameter to widen the scope. `expand` : `str`, optional. Comma-separated relationship names to include inline, every field. `fields` : `list[str]`. Fields to read, comma-separated or one per repeated parameter; dotted paths reach through relationships. Every scalar of the resource when omitted. Returns ---- `object` : `GraphObjectResponse`. The requested object. Examples ----- ``` GET /v1/graph/address_identities/QWRkcmVzc09iamVjdDox?expand=addressVerificationEvents GET /v1/graph/consumer_identities/Q29uc3VtZXJPYmplY3Q6MQ==?fields=firstName,address_identities.city ``` # Create or renew an institution attestation for a product. Source: https://docs.solo.one/api-reference/institution-attestation-event/institution-attestation-event POST /v1/institution-attestation-event Create a new institution-level attestation scoped to the caller's entity and product. The attestation is valid for 12 months from the time it is created. Creating a new attestation does not revoke prior ones; the most recently created active attestation for a given product is treated as the current one. Parameters ---------- `body` : `InstitutionAttestationEventCreateRequest`. Attestation payload. Returns ------- `response` : `InstitutionAttestationEventResponse`. The newly created attestation record. # API Reference Source: https://docs.solo.one/api-reference/introduction SOLO Network API endpoint reference The SOLO Network API reference is auto-generated from the [OpenAPI specification](/api-reference/openapi.json). Each endpoint includes request/response schemas, parameter details, and example payloads. New to the platform? Read [High-level concepts](/home/overview) first — the reference assumes familiarity with entities, products, policies, networks, consent, and entitlement. ## Authentication All endpoints are authenticated with a Bearer token. See [Authentication](/home/authentication). ``` Authorization: Bearer ``` ## How the reference is organized > **Consent** > > Create and read [consent](/concepts/identity/consent) records for consumers and > businesses. Required before querying. > **Entities** > > Search consumers and businesses within a [network](/concepts/governance/networks). > **Products** > > Query and furnish [products](/api-overview/querying/products) — KYC/KYB certificates and > screening lists. > **Policies** > > Create and configure querying [policies](/concepts/governance/querying-policies), and list > furnishing policies. > **Attestations** > > Create or renew an institution attestation for a product. > **Bulk ingestion** > > Upload files for ingestion. See also the [SFTP guide](/api-overview/sftp/overview). ## Conventions * **Products** expose `query` (read) and `furnish` (write) operations under `/v1/products/{product}/…`. * **Querying** a product requires a `consent_id` and one or more `network_policy` pairs. See [Querying](/api-overview/querying/overview). * **Furnishing** a product requires a `network_id` and `application_date`. See [Furnishing](/api-overview/furnishing/overview). * Errors follow a consistent JSON shape — see [Errors](/home/errors). # Furnish KYB certificate data via structured JSON Source: https://docs.solo.one/api-reference/kyb-certificate-snapshot/furnish POST /v1/products/kyb_certificate_snapshot/furnish # Query consolidated KYB certificate data for a business Source: https://docs.solo.one/api-reference/kyb-certificate-snapshot/query POST /v1/products/kyb_certificate_snapshot/query # Soft check whether a KYB certificate could be issued (non-billable dry run) Source: https://docs.solo.one/api-reference/kyb-certificate-snapshot/soft POST /v1/products/kyb_certificate_snapshot/query/soft # Furnish KYC certificate data via structured JSON Source: https://docs.solo.one/api-reference/kyc-certificate-snapshot/furnish POST /v1/products/kyc_certificate_snapshot/furnish # Query consolidated KYC certificate data for a consumer Source: https://docs.solo.one/api-reference/kyc-certificate-snapshot/query POST /v1/products/kyc_certificate_snapshot/query # Soft check whether a KYC certificate could be issued (non-billable dry run) Source: https://docs.solo.one/api-reference/kyc-certificate-snapshot/soft POST /v1/products/kyc_certificate_snapshot/query/soft # Soft check: per-furnisher field coverage for a product and profile Source: https://docs.solo.one/api-reference/product-coverage-check/check POST /v1/products/check # Save per-field selections onto an existing querying policy. Source: https://docs.solo.one/api-reference/querying-policies/configuration PUT /v1/networks/policies/querying/{policy_id}/configuration Persist per-field selections onto an existing querying policy. Replaces the policy's per-model and per-field selections with the submitted set. The policy is left in ``published`` status. # Create a network-wide querying policy. Source: https://docs.solo.one/api-reference/querying-policies/querying POST /v1/networks/policies/querying Create a new network-wide querying policy. Per-model and per-field selections for the policy — which may span any product's models — are authored separately via ``PUT /networks/policies/querying/{policy_id}/configuration``. # Entitlement Source: https://docs.solo.one/concepts/governance/entitlement Why you can read a given field about an entity **Entitlement** is what determines *which data* you may read about an entity. It is the third and most granular layer of SOLO's access model: | Layer | Grants | Question it answers | | - | - | - | | [Network membership](/concepts/governance/network-roles) | **Capability** | *May I call this kind of operation on this network at all?* | | [Consent](/concepts/identity/consent) | **Permission** | *May I query this particular subject?* | | **Entitlement** | **Visibility** | *Of the data that exists, which records and fields may* ***I*** *see?* | Membership lets you ask; consent makes the question lawful; entitlement decides what the answer contains. The principle is simple: **you can read what you have contributed or previously looked at.** You earn entitlement to an entity's data by participating in the network — not by your role, and not by network membership alone. ```mermaid theme={null} flowchart LR F[You furnish an entity] --> Ent[Entitlement ledger] Q[You query an entity] --> Ent G[A field access grant
is created with consent] --> Ent Ent --> Read[Readable data on future queries] ``` ## Where entitlement comes from SOLO maintains, per participant, a ledger of which records that participant has earned the right to read. Entries land in the ledger three ways. ### Furnishing When you **furnish** data for an entity, SOLO records an entitlement link for each record you contributed, tied to your entity and the subject's profile. On future queries for that subject, those records — and the consolidated results built from them — are readable by you. This is the most direct path: furnish what you know about a consumer or business, and subsequent queries for that subject return data — yours, plus anything else you're entitled to — consolidated into one result. ### Querying When you **query** an entity (with valid [consent](/concepts/identity/consent)), SOLO records entitlement links for the records that query actually read. Having lawfully queried a subject, you remain entitled to the data that query covered. Entitlement therefore accrues over time. Each legitimate interaction — furnishing or querying — widens the set of data you can see for the entities you've actually engaged with. At read time, SOLO takes the **union** of your furnish-derived and query-derived entitlements. ### Field access grants The third source is explicit: a **field access grant**. A grant ties a [consent](/concepts/identity/consent) record to a *furnishing entity* and enumerates the specific field definitions you may read from that furnisher's data about the subject. Grants are created automatically when a consent record is created, and they are returned when you read the consent back: ```json theme={null} { "consent_id": "a3f0b9c7-…", "field_access_grants": [ { "furnishing_entity_id": "7c2d91e4-…", "field_definitions": ["consumer.first_name", "consumer.last_name"], "effective_from": "2026-06-01", "effective_to": null } ] } ``` Three properties of grants matter operationally: * **Effective windows.** A grant carries `effective_from` and `effective_to` dates. A grant outside its window contributes nothing. * **Revocation.** A revoked grant stops contributing immediately, regardless of its window. * **Consent expiry.** Grants ride on the consent they belong to — when the consent expires, its grants stop expanding your access. While a grant is active, reads on the subject can draw on the named furnishing entity's data for the granted fields — even data you didn't furnish and hadn't previously read. ## How entitlement is enforced Entitlement is enforced at **read time, per record**. When you run a query, SOLO loads your ledger for the subject — the union of everything you've earned through furnishing and through prior queries — and filters the candidate records down to the ones the ledger covers. Records you aren't entitled to are simply not consulted when the consolidated result is built; they don't appear redacted, they don't appear at all. ```mermaid theme={null} sequenceDiagram participant You as Your query participant SOLO as SOLO participant Ledger as Entitlement ledger participant Data as Network data You->>SOLO: query subject (consent_id, network_ids) SOLO->>Ledger: load your entitlements for this subject Ledger-->>SOLO: records earned via furnish + query + grants SOLO->>Data: read only entitled records Data-->>SOLO: rows SOLO-->>You: consolidated result (policy-capped) ``` Three consequences follow from this design: * **Entitlement is per subject.** Furnishing one consumer earns you nothing about a different consumer, even one furnished by the same institution into the same network. * **Entitlement is per participant.** It attaches to *your* entity. It is not shared across a network, not inherited from a parent network, and not transferred when roles change hands. * **Entitlement doesn't expire on its own.** Furnish- and query-derived entitlements persist; only field access grants carry effective windows and revocation. (Consent, by contrast, can expire — and you always need valid consent to ask in the first place.) ## Putting it together A query result is shaped by three layers, applied in order: **1. Consent** You hold a valid [consent](/concepts/identity/consent) record for the subject — you're permitted to ask. **2. Policy ceiling** The network's [querying policy](/concepts/governance/querying-policies) defines the maximum set of fields this product can expose in this network — for anyone. **3. Entitlement filter** Of those fields, you receive only what you've **earned** — through prior furnishing or querying of this entity, or through an active field access grant. The result is the **intersection** of what the policy allows and what you're entitled to: | Question | Answered by | | - | - | | *In this network, what is this product allowed to expose at all?* | **Querying policy** — what *may* be read | | *Of that, what have I personally earned the right to read?* | **Entitlement** — what *you* may read | > [!NOTE] > > This is why two participants can run the *same* query on the *same* entity > and get *different* results: each sees only the data they've earned access > to. The policy is shared; the entitlement ledger is per-participant. ## Worked examples The JSON below is illustrative sketches, trimmed to the fields that matter for each example. ### Example A: a furnisher reads back its own furnished fields Coastal Bank furnishes a KYC record for Jane Doe into its network: ```bash theme={null} curl -X POST https://api.solo.one/v1/products/kyc_certificate_snapshot/furnish \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "network_id": "9f1c0c2e-…", "application_date": "2026-05-28", "records": [{ "first_name": "Jane", "last_name": "Doe", "date_of_birth": "1990-01-15", "social_security_number": "123-45-6789" }] }' ``` The furnish creates entitlement links between Coastal and every record it contributed. When Coastal later queries Jane (with consent), the consolidated result includes the data Coastal furnished — no extra setup, no grant needed: ```json theme={null} { "results": [{ "meta": { "network_id": "9f1c0c2e-…", "policy_id": "5e7d2a14-…" }, "document_capture": { "…": "populated from Coastal's furnished records" }, "biometric_capture": { "…": "populated" } }] } ``` ### Example B: a querier sees gaps because it lacks entitlement Summit Bank — a member of the same network, with the querier role and a valid consent for Jane — runs the *identical* query. Summit has never furnished or queried Jane, and no grant names Summit. The query is **authorized** (membership * consent), and the policy would allow the fields — but Summit's entitlement ledger has nothing for Jane: ```json theme={null} { "results": [{ "meta": { "network_id": "9f1c0c2e-…", "policy_id": "5e7d2a14-…" }, "document_capture": null, "biometric_capture": null }] } ``` Sections come back empty rather than erroring — an entitlement gap looks like *missing data*, not like a `403`. For certificate products, if the entitled data can't satisfy the policy's requirements at all, the API returns **`204 No Content`** instead of issuing a certificate. ### Example C: a field access grant expands the result Jane now consents to Summit's inquiry, and the consent's field access grant names Coastal as the furnishing entity whose data Summit may read: ```json theme={null} { "consent_id": "b8e1d2f0-…", "field_access_grants": [{ "furnishing_entity_id": "", "field_definitions": ["consumer.first_name", "consumer.last_name", "consumer.date_of_birth"], "effective_from": "2026-06-01", "effective_to": "2027-06-01" }] } ``` Summit re-runs the same query — before and after: **Before (no grant)** ```json theme={null} { "results": [{ "meta": { "network_id": "9f1c0c2e-…", "policy_id": "5e7d2a14-…" }, "document_capture": null }] } ``` **After (grant active)** ```json theme={null} { "results": [{ "meta": { "network_id": "9f1c0c2e-…", "policy_id": "5e7d2a14-…" }, "document_capture": { "first_name": "Jane", "last_name": "Doe", "date_of_birth": "1990-01-15" } }] } ``` The grant widened Summit's visibility into Coastal's furnished data for the granted fields, for the grant's effective window. And because Summit has now lawfully *queried* Jane, those reads are themselves recorded — Summit stays entitled to what this query covered, even after the grant's window closes. ## What entitlement is not * **Not a role.** Being a [governor](/concepts/governance/networks#permissions--roles) of a network does not entitle you to members' data. * **Not network containment.** Sharing a network with another participant does not entitle you to what they furnished. * **Not retroactive by configuration.** Changing roles, network settings, or [governance rules](/concepts/governance/network-governance) does not grant access to data you didn't earn through your own furnishing, querying, or an explicit grant. * **Not a bypass of policy.** An entitlement (or grant) to a field the [querying policy](/concepts/governance/querying-policies) excludes still returns nothing — the policy is the ceiling. ## Troubleshooting: "why is this field missing?" Work down this checklist when a query succeeds but a field you expected is absent: 1. **Is the field enabled in the querying policy?** The policy caps every reader. Check the policy's field configuration — see [Querying Policies](/concepts/governance/querying-policies). 2. **Have you furnished this subject?** If not, you have no furnish-derived entitlement to their records. 3. **Have you previously queried this subject?** Query-derived entitlement only covers what earlier queries actually returned. 4. **Is there a field access grant for you — and is it alive?** Check the consent record's `field_access_grants`: is the field listed in `field_definitions`, is today inside `effective_from`/`effective_to`, has the grant been revoked, has the consent expired? 5. **Was the data furnished at all?** Entitlement filters existing data; it can't surface a field nobody contributed. A [coverage check](/api-overview/querying/coverage-check) tells you whether data exists before you spend a query. 6. **Are you querying the right network scope?** Data is network-bounded; see [Network Governance](/concepts/governance/network-governance) for which networks your query can reach. If all six check out and the field is still missing, contact your SOLO account manager with the `request_id` from the response. ## Related concepts > **[Consent](/concepts/identity/consent)** > > The permission layer that gates whether you may query at all — and carries > field access grants. > **[Querying Policies](/concepts/governance/querying-policies)** > > The per-network ceiling on what a product can expose. > **[Querying](/api-overview/querying/overview)** > > How entitlement is applied when a query runs. > **[Furnishing](/api-overview/furnishing/overview)** > > Contributing data — the most direct way to earn entitlement. # Furnishing Policies Source: https://docs.solo.one/concepts/governance/furnishing-policies How a network turns furnished records into network data A **furnishing policy** — called a **mapping** in the dashboard — defines how one type of furnished record is **mapped, validated, and persisted** into a [network](/concepts/governance/networks). Where a [querying policy](/concepts/governance/querying-policies) governs what leaves the network on a read, a furnishing policy governs what happens to data on the way in. As a furnisher you never name a mapping yourself. Every record in a [JSONL file](/api-overview/furnishing/file-upload) carries a `_type`, and the network sends the record to each active mapping that claims that type. Mappings are authored and managed by the network's [governor](/concepts/governance/network-roles). ```mermaid theme={null} flowchart LR F[File record
_type] --> R{Routing} R --> P[Mapping] P --> S1[Map identity] S1 --> S2[Match or create entity] S2 --> S3[Map events] S3 --> S4[Persist] S4 --> Store[(Network data)] ``` ## Feeds and record types Mappings are created from a **feed** — a stream of records arriving from one source, such as an SFTP drop path. The feed declares a sample record: * The sample's `_type` is the **record type** the feed's mappings claim. * The sample's fields are the **source schema** the mapping reads from. A record whose `_type` no active mapping claims is counted as unmatched and skipped; it never fails the file. ## The pipeline model Under the hood, a mapping is a **pipeline**: an ordered sequence of steps that every claimed record runs through. Each mapping is derived from a SOLO-maintained default pipeline, identified by its `source_default_slug`: | `source_default_slug` | Records it ingests | | - | - | | `jsonl_mapping_default` | Consumer records — identity plus KYC verification events | | `jsonl_mapping_business_default` | Business records — identity plus KYB verification events | The governor authors two mapping documents, in the mapping's **Mapping** tab in the dashboard: | Document | What it controls | | - | - | | **Identity mapping** | Which record fields identify the consumer or business (for example SSN and date of birth), used to match an existing profile or create one. | | **Tree mapping** | Which verification events the record becomes, and which record fields fill each event's fields. | The dashboard verifies both documents against the source schema; a mapping can't be activated until they verify. ## Lifecycle A mapping moves between two states: ```mermaid theme={null} flowchart LR D[Draft
editable, not routing] -->|activate| A[Active
frozen, routing] A -->|deactivate| D ``` * **Draft** — the configuration can be edited freely. Drafts never receive records. * **Active** — the configuration is frozen and the mapping receives every record that carries its record type. To change it, deactivate it, edit, and activate again. | Field | Meaning | | - | - | | `name` | Display name. Together with `network_id` and `version`, must be unique. | | `version` | Integer version, starting at 1. | | `schema_version` | Version of the pipeline document schema the mapping conforms to. | | `source_default_slug` | Which default pipeline the mapping is derived from. | | `is_active` | Whether the mapping currently receives records. | | `mapping_metadata` | Routing and authoring metadata, including the claimed record type. | ## Reading policies via the API Any member of a network can list and read its furnishing policies. Authoring happens in the dashboard. ### List policies in a network ```bash theme={null} curl -G https://api.solo.one/v1/networks/policies/furnishing \ -H "Authorization: Bearer $SOLO_TOKEN" \ --data-urlencode "network_id=9f1c0c2e-…" \ --data-urlencode "limit=20" \ --data-urlencode "offset=0" ``` `limit` accepts 1–100 (default 20); `offset` ≥ 0 (default 0). Returns an array: ```json theme={null} [ { "id": "4f6e2a90-…", "entity_id": "7c2d91e4-…", "network_id": "9f1c0c2e-…", "name": "Coastal KYC intake", "version": 1, "schema_version": "1", "source_default_slug": "jsonl_mapping_default", "is_active": true, "mapping_metadata": { "selector": { "path": "/_type", "op": "eq", "value": "kyc_certificate" } }, "created_at": "2025-12-12T18:03:11Z", "updated_at": "2025-12-30T09:41:00Z" } ] ``` ### Read a single policy ```bash theme={null} curl -G https://api.solo.one/v1/networks/policies/furnishing/4f6e2a90-… \ -H "Authorization: Bearer $SOLO_TOKEN" \ --data-urlencode "network_id=9f1c0c2e-…" ``` The `network_id` query parameter scopes the lookup; a policy that exists but isn't visible in that network returns `404` `RESOURCE_NOT_FOUND`. ## Relationship to other concepts | Concept | Relationship | | - | - | | [Querying policies](/concepts/governance/querying-policies) | The read-side counterpart. A network typically has both: mappings shaping intake, querying policies capping reads. | | [Entitlement](/concepts/governance/entitlement) | Furnishing is what *earns* a furnisher read access to the subjects it contributed. | | [Networks](/concepts/governance/networks) | Mappings belong to a network and only receive records furnished into it. | | [Furnishing](/api-overview/furnishing/overview) | The furnisher-facing view: send records with a `_type`, and the mapping applies itself. | ## In the dashboard ![KYC product detail — policies tab](https://mintcdn.com/soloone/9HdwBb7FHoPnTmO1/images/dashboard/journeys/furnishing/furnishing-complete/03-product-policies-tab.png?fit=max\&auto=format\&n=9HdwBb7FHoPnTmO1\&q=85\&s=ace2f9307badb2178ef903b432ce2876) ![KYB product detail — Furnishing Policies tab](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/17-product-detail-kyb-furnishing-policies.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=a624ca8cb1960e4290fe38cff9c87e9a) ## Related concepts > **[Furnishing](/api-overview/furnishing/overview)** > > The furnisher's view of the same flow. > **[Querying Policies](/concepts/governance/querying-policies)** > > Field-level read rules — the other half of policy governance. > **[JSONL Files](/api-overview/furnishing/file-upload)** > > The file format mappings read. > **[SFTP Ingestion](/api-overview/sftp/overview)** > > Scheduled delivery of JSONL files. # Network Governance Source: https://docs.solo.one/concepts/governance/network-governance How query access flows between related networks **Network governance** is the set of rules that decide whether a querier in one network is allowed to read data scoped to another. Governance sits on top of the network tree (see [Networks](/concepts/governance/networks)) and the role model (see [Network Roles](/concepts/governance/network-roles)). The model has two parts: a small set of paths that are **always allowed**, and a small set of **opt-in rules** that a network's governor can turn on to permit further query paths. ## How these rules work: authorization vs data visibility Every governance check answers two questions, in order: 1. **Authorization** — *can* this querier make this query at all? The answer is yes or no. If no, the API returns an authorization error and no data comes back. 2. **Data visibility** — *given* the query is allowed, *what records* come back? The answer is a set, which may be large, small, or empty. Most of the rules described on this page are about authorization — they decide whether a query is allowed. One rule, `auto_include_descendants`, is purely about visibility — it widens what comes back once a query is already allowed, but it can never authorize a query on its own. The decision matrix at the end of this page is the authorization view; visibility behavior is called out inline with each rule. This distinction matters in practice. An authorization failure surfaces as a clear error; a visibility filter that excludes all rows surfaces as a successful query with zero results. ## Querier role is the gate Throughout this page, "querier on N" means an entity that participates on network N with the **querier** role. Running a product query is a querier's job — see [Network Roles](/concepts/governance/network-roles) — so the governance checks below are described from the querier's point of view: can a querier on one network read data scoped to another? Two clarifications keep this honest: * **Governance authorizes scope, not content.** Passing a governance check means the query is allowed to *target* the network. What actually comes back is still filtered by [entitlement](/concepts/governance/entitlement) and capped by the [querying policy](/concepts/governance/querying-policies) — a governor or furnisher who is also a member does not, by virtue of those seats, see other members' data. * **The authorization check consults network membership.** SOLO's scope check asks which networks the calling entity participates in. Holding the querier role is the operational prerequisite for product queries; treat the role model as the contract even where the scope check is membership-based today. ## Always-allowed query paths These hold for every network. They are built into the model — there is no setting that controls them, and they cannot be turned off. | If the querying entity is… | …it can query data scoped to… | | - | - | | A querier on network **N** | network **N** | | A querier on any **ancestor** of network **N** | network **N** ("query your descendants") | The intuition: a bank that holds the querier role on a network can always see data in any child network it runs underneath that network, even if it doesn't separately hold the querier role on each child. A querier in a specific child network can always see that network's data. Everything else — querying upward, querying across to a cousin — is **denied by default** unless one of the rules below is explicitly enabled. ## Opt-in rules Each opt-in rule is a setting **stored on a specific network** — the network whose data is being opened up. When SOLO evaluates a governance check, it reads the rule from that network and nowhere else. Turning a rule on at one network has no effect on any other network, including its ancestors, descendants, or cousins. A network's governor can enable any of the following three rules. ### `ancestor_query` — let descendants query upward **Type:** Authorization rule. **Stored on:** the ancestor network (the one being opened up). When this rule is enabled on a network, a querier on any **descendant** of that network is authorized to query data scoped to the network itself. **Example:** Coastal Bank turns on `ancestor_query` on its own network. A querier in *Coastal — Credit Cards* (a child network underneath Coastal Bank) can now query *Coastal Bank* data directly. ### `cousin` — let cousins query each other **Type:** Authorization rule. **Stored on:** the **closest** common ancestor of the two cousin networks. When this rule is enabled, a querier on any network underneath that ancestor is authorized to query data scoped to any other network underneath the same ancestor — even though neither is in the other's direct line. **Example:** A Federation root turns on `cousin` on its own network. A querier in *Coastal Bank* can now query *Summit Bank* data, because both networks share the Federation as their closest common ancestor. > [!WARNING] > > The rule must sit on the *closest* common ancestor. Setting `cousin` on a higher ancestor — say, an ancestor of the Federation — has no effect on cousins underneath the Federation. Each cousin pair consults exactly one network's setting: their closest shared ancestor. ### `auto_include_descendants` — expand a query to cover descendants **Type:** Visibility rule. **Stored on:** the parent network whose descendants should be pulled into queries against it. This rule does not change *who* can query — it changes *what comes back*. When enabled, a query scoped to a network automatically pulls in data from all of its descendants as well. **Example:** Coastal Bank turns on `auto_include_descendants` on its own network. A querier authorized to read *Coastal Bank* data automatically gets data from *Coastal — Credit Cards*, *Coastal — Mortgages*, and any other network underneath Coastal in the same query — without having to query each one separately. > [!NOTE] > > `auto_include_descendants` is a *scope expander*, not an *authorization*. The querier still needs to be separately authorized to query the parent network — via a direct querier role, a querier role on an ancestor, or one of the authorization rules above. > [!NOTE] > > **No descendant membership required.** When `auto_include_descendants` is enabled, the descendants' data flows into the parent query automatically — the querier does not need to hold a querier role on each descendant network. This is what makes it a visibility rule rather than an authorization rule: a senior network's governor can declare "anyone authorized to query me can also see what flows through my children," without each querier separately enrolling on every descendant. ## Putting it together: the authorization matrix Here's the complete authorization matrix for whether a querier can read data scoped to a given network. The matrix covers **only the authorization question** — the yes/no decision about whether the query is allowed at all. Once a query is authorized, `auto_include_descendants` may additionally widen what comes back; that visibility behavior is separate from the matrix below. The third column shows **which network holds the setting** SOLO consults — that is, which governor controls the answer. | Querier's network in relation to the target | Authorized? | Where the controlling rule lives | | - | - | - | | Querier on the target | Always | — (built in, no rule) | | Querier on an ancestor of the target | Always | — (built in, no rule) | | Querier on a descendant of the target | Only if `ancestor_query` is enabled | On the target network | | Querier on a cousin of the target | Only if `cousin` is enabled | On the closest common ancestor | | Querier with no relationship to the target | Never | — (no rule grants this) | ## Worked example 1: a sponsor bank and its fintech partner networks Harbor Bank sponsors two fintechs. Each fintech's offering runs as a **partner network** under Harbor's network: ```mermaid theme={null} graph TD Harbor[Harbor Bank — standard network] Lendly[Lendly — Personal Loans partner network] CardCo[CardCo — Charge Cards partner network] Harbor --> Lendly Harbor --> CardCo ``` Harbor holds querier (and governor) on its own network. Lendly and CardCo each hold furnisher and querier on **their own network only**. **What works out of the box:** * **Lendly queries Lendly.** A querier on a network can always query it. Same for CardCo. * **Harbor queries either partner network.** Harbor is an ancestor of both, and "query your descendants" is built in. Harbor does not need a seat on each one. **What is denied by default:** * **Lendly queries Harbor.** That's querying *upward*. It stays denied until Harbor's governor enables `ancestor_query` **on Harbor's own network** — Lendly's governor cannot flip that switch. * **Lendly queries CardCo.** They are cousins (siblings, sharing Harbor as parent). Denied until Harbor — their closest common ancestor — enables `cousin`. Most sponsors deliberately leave this off: it's exactly the wall that keeps one fintech's customers invisible to another. **One convenience Harbor will likely want:** enabling `auto_include_descendants` on Harbor's network means a single Harbor-scoped query also returns data flowing through *Lendly* and *CardCo*, without Harbor issuing one query per partner network. This is a visibility rule — it widens Harbor's already-authorized query; it grants nothing to the fintechs. Even with every rule enabled, [entitlement](/concepts/governance/entitlement) still applies: Harbor sees consolidated results only for the subjects it has furnished or lawfully queried, and the fields are capped by the [querying policy](/concepts/governance/querying-policies). Governance opens the door to the room; it doesn't hand over the filing cabinet. ## Worked example 2: two cousin banks in a federation Coastal Bank and Summit Bank both participate in a data-sharing federation. Each bank runs child networks of its own: ```mermaid theme={null} graph TD Fed[Federation Root] Coastal[Coastal Bank] Summit[Summit Bank] CCards[Coastal — Credit Cards] SAuto[Summit — Auto Loans] Fed --> Coastal Fed --> Summit Coastal --> CCards Summit --> SAuto ``` **Initially**, with no opt-in rules anywhere: * Coastal can query Coastal and *Coastal — Credit Cards* (descendant). It **cannot** query Summit or *Summit — Auto Loans*: Summit is a cousin, and Summit's child network is also a cousin (common ancestor: the Federation). * The Federation root's querier (if the federation operator holds one) can query every network in the tree — everything is its descendant. **The federation decides cousins should be able to screen against each other's data.** The fix is one switch in one place: the Federation governor enables `cousin` **on the Federation root** — the closest common ancestor of Coastal and Summit. Now a Coastal querier can scope queries to Summit (and vice versa), and likewise across the cousin networks. **Common mistakes in this topology:** * **Setting `cousin` on Coastal or Summit does nothing.** The rule is consulted on the closest common ancestor, not on either leaf. Neither bank can open (or, alone, close) the cousin path — that authority sits with the Federation governor, which is the point: cross-bank sharing is a federation-level agreement. * **Expecting `cousin` on a network *above* the Federation to work.** If the Federation itself had a parent, enabling `cousin` there would not authorize Coastal ↔ Summit; only their *closest* shared ancestor's setting is consulted. * **Expecting Coastal to see Summit's child networks in one query.** Authorizing Coastal → Summit doesn't bundle in *Summit — Auto Loans*. For that, Summit's governor would additionally enable `auto_include_descendants` on Summit's network, widening any authorized Summit-scoped query to include its children. And as always: authorization gets Coastal's query in the door at Summit; what rows come back is governed by Coastal's [entitlement](/concepts/governance/entitlement), and which fields appear is governed by the applicable [querying policy](/concepts/governance/querying-policies). A bank that has never furnished or queried a given consumer gets an empty (but successful) result, not a treasure trove. ## Who can change governance rules Only the **governor entity** of a network can change that network's governance rules. A governor cannot change rules on a parent, child, or cousin network without separately being its governor. The governor entity is the single entity recorded as the owner of the network when it was created. See the footnote on [Network Roles](/concepts/governance/network-roles) for the distinction between the governor entity and entities holding the governor role. There is no field-level permissioning *inside* the governance settings today — a governor who can edit the network at all can change any of the three rules. ## Rules can only be enabled from above Because each rule is stored on the network being opened up — not on the network that wants access — only the governor of the *opening* network can turn it on. This has an important consequence: > A child network's governor cannot grant themselves visibility into the parent by enabling `ancestor_query`. That switch lives on the *parent*, and only the parent's governor can flip it. The same property holds for `cousin`: the rule sits on the closest common ancestor of the two cousin networks, so opening up cross-cousin visibility requires a governor at that ancestor — not a governor at either of the two leaf networks. This is a deliberate property of the model. Access flows from the top down, configured by the senior network's governor; a junior network cannot elevate itself into a senior network's data. > [!NOTE] > > If the *same* entity happens to be the governor of both networks (for example, a bank that governs both itself and a child network underneath it), that entity can edit both networks' settings — but that is the same entity already holding authority over both networks, not a child governor reaching up. ## Current limitations > [!WARNING] > > The governance rule format is provisional and **subject to change**. The three keys above (`ancestor_query`, `cousin`, `auto_include_descendants`) represent the rules SOLO supports today. We expect the surface area to grow — for example, with per-entity overrides, role-specific rules, or expiry windows — and the key names may change as the schema matures. This page will be updated when the schema is finalized. Other things to be aware of in the current model: * **Rules are network-wide.** A rule applies uniformly to every querier it could affect. There is no way today to enable `cousin` for some cousin networks but not others. * **No validation on unknown keys.** Anything else written into the governance settings is silently ignored. Be careful about typos — `ancestor_querys` (with an s) will read as "off" rather than producing an error. If you need behaviour that the current rules don't express, contact your SOLO account manager — we are actively gathering input on the next iteration of the governance model. ## What's next * See [Networks](/concepts/governance/networks) for the underlying tree structure. * See [Network Roles](/concepts/governance/network-roles) for who can participate in a network and how. # Network Roles Source: https://docs.solo.one/concepts/governance/network-roles The three ways an entity can participate in a network Every entity that participates in a [network](/concepts/governance/networks) does so in one or more **roles**. The role determines what the entity is allowed to do on that network — administer it, supply data to it, or query data from it. ## The three roles > **Governor** > > Administers the network. Governors manage settings, add and remove participants, and configure how data is allowed to flow between this network and others. > **Furnisher** > > Supplies data into the network. Furnishers write records that other participants can later query, subject to the network's governance rules. > **Querier** > > Reads data from the network. Queriers retrieve records that have been furnished by other participants in the network. ## What each role is responsible for ### Governor The governor is the network's administrator and the accountable party for its configuration. In practice a governor: * **Manages the participant roster** — adds entities to the network with the roles they need, and removes them when an arrangement ends. * **Owns the policy surface** — creates and configures the [querying policies](/concepts/governance/querying-policies) and [furnishing policies](/concepts/governance/furnishing-policies) that govern what the network reads and accepts. * **Sets governance rules** — decides whether related networks may query this one (see [Network Governance](/concepts/governance/network-governance)). A governor seat is **not** a window into other members' data. Reading data still requires the querier role plus [consent](/concepts/identity/consent) and [entitlement](/concepts/governance/entitlement), like any other participant. ### Furnisher The furnisher contributes data. A furnisher: * **Furnishes product records** — calls `POST /v1/products/{product}/furnish` naming the network, or delivers [JSONL files](/api-overview/furnishing/file-upload) from the dashboard or over [SFTP](/api-overview/sftp/overview). * **Earns entitlement by contributing** — each entity a furnisher contributes data about becomes an entity whose consolidated data the furnisher may read back on future queries. See [Entitlement](/concepts/governance/entitlement). A furnisher does not need to know which furnishing policy applies — each file record's `_type` routes it to the network's mappings that claim that type. ### Querier The querier consumes data. A querier: * **Creates consent records** — querying a consumer or business requires a valid [consent](/concepts/identity/consent), so the consent endpoints are part of the querier's day-to-day surface. * **Runs product queries** — calls `POST /v1/products/{product}/query` scoped to the networks it participates in (or networks reachable through [governance rules](/concepts/governance/network-governance)). * **Searches entities** — looks up consumers and businesses within the network before querying. What a query returns is never determined by the role alone: the [querying policy](/concepts/governance/querying-policies) caps what the product may expose in that network, and [entitlement](/concepts/governance/entitlement) narrows it to what the caller has earned. ## Role-to-endpoint capability matrix The table below maps the public `/v1` API surface to the role that exercises it. ✓ marks the role an operation belongs to; — means the operation is not part of that role's job. | Endpoint | Governor | Furnisher | Querier | | - | :-: | :-: | :-: | | `POST /v1/consent/consumer_identity` | — | — | ✓ | | `GET /v1/consent/consumer_identity/{consent_id}` | — | — | ✓ | | `PUT /v1/consent/consumer_identity/{consent_id}` | — | — | ✓ | | `POST /v1/consent/business_identity` | — | — | ✓ | | `GET /v1/entities/consumer_identity/search` | ✓ | ✓ | ✓ | | `GET /v1/entities/business_identity/search` | ✓ | ✓ | ✓ | | `POST /v1/products/{product}/query` | — | — | ✓ | | `POST /v1/products/check` (coverage check) | — | — | ✓ | | `POST /v1/products/{product}/furnish` | — | ✓ | — | | `POST /v1/networks/policies/querying` | ✓ | — | — | | `PUT /v1/networks/policies/querying/{policy_id}/configuration` | ✓ | — | — | | `GET /v1/networks/policies/furnishing` | ✓ | ✓ | ✓ | | `GET /v1/networks/policies/furnishing/{policy_id}` | ✓ | ✓ | ✓ | | `POST /v1/institution-attestation-event` | ✓ | ✓ | ✓ | > [!NOTE] > > **How to read this matrix.** It describes the *operational model* — which role > each operation belongs to in a well-run network. Enforcement happens at > different layers depending on the operation. Network administration (managing > memberships, accepting policy definitions) is hard-enforced > against the network's governor. Reads are enforced through network scoping, > [consent](/concepts/identity/consent), and > [entitlement](/concepts/governance/entitlement) — so calling a query endpoint > without the underlying permissions yields an authorization error or an empty > result rather than data. Don't treat a missing per-endpoint role check as a > grant of access; SOLO may tighten per-endpoint enforcement over time. Rows marked for all three roles are member-level operations: any participant can search the directory of entities within its network scope, read the furnishing policies visible in a network it belongs to, or attest for its own institution. ## What each role gates Each role gates a specific kind of action on the network, and the three gates are independent of one another: | Role | Gates | | - | - | | **Governor** | Editing the network's settings, adding and removing participants, configuring governance rules, authoring policies | | **Furnisher** | Writing data into the network | | **Querier** | Reading data from the network | The gates do not overlap. An entity holding only the furnisher role can write data but cannot read it back out — reading requires the querier role on the same network (or, via [Network Governance](/concepts/governance/network-governance), on a related one). The same is true in reverse: a querier cannot furnish data. This is why most operators who both supply and consume data on a network hold both the furnisher and querier roles. ## Roles are additive An entity can hold more than one role on the same network. A bank that operates a network it also furnishes into and queries from will typically hold all three roles. SOLO records each role as a separate participation, so revoking one role does not affect the others. The same entity cannot be granted the same role twice on the same network — each combination of network, entity, and role is unique. ## Roles are network-specific Roles do not transfer between networks. An entity that is a governor of one network has no automatic role on its parent, child, or cousin networks. To participate elsewhere, the entity must be added explicitly to each network it operates on. What *does* transfer is **query access** — a querier on one network may, depending on governance rules, be allowed to query data scoped to a related network. See [Network Governance](/concepts/governance/network-governance) for the full rules. ## Who can change roles Only a governor of a network can add or remove participants on that network. A governor of a parent network cannot directly add or remove roles on a child network — they would need to be a governor of the child as well. Two safety rules apply, and both are enforced server-side: * **A network must always have at least one governor.** SOLO rejects any change — demotion, reassignment, or removal — that would leave a network with zero governors. * **An entity can always remove its own participation** in a network (as long as doing so wouldn't violate the rule above). Attempts that violate these rules fail with an authorization error in the standard envelope (see [Errors](/home/errors)): ```json theme={null} { "detail": "Permission denied", "error_code": "PERMISSION_DENIED" } ``` ## What's next * See [Network Governance](/concepts/governance/network-governance) for how participation translates into query access across the network tree. * See [Entitlement](/concepts/governance/entitlement) for why holding a role is necessary but not sufficient to read data. ## In the dashboard ![Network created — detail page with new network](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/networks/network-complete/02-create-success-detail.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=2eab76203708081b33499734e5ac3771) ![Network detail — overview](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/networks/network-complete/03-detail-overview.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=76c1a4902b0efb62888375a5d7cb8e93) *** > [!NOTE] > > **A note on governors.** SOLO currently distinguishes between a network's *governor entity* (a single entity recorded on the network itself, set when the network is created) and entities holding the *governor role* (which can be more than one). In practice these are kept in sync — the entity that creates a network is recorded as the governor entity and is also granted the governor role automatically. Today, only the entity recorded as the governor entity can edit the network's settings, even if other entities hold the governor role. This distinction is under review and may be removed in a future release; we will update this page when the decision is final. # Networks Source: https://docs.solo.one/concepts/governance/networks The trust boundary that groups participants, roles, products, and policies A **network** is the trust boundary of p SOLO platform. It groups the participants who have agreed to share data, the **products** they can use, the **policies** that govern those products, and the data that gets furnished. Everything you do — furnishing, querying, applying policies — happens *within* a network you belong to. ## What a network represents A network is a logical grouping that ties together three things: * **Participants** — the entities that operate within the network (see [Network Roles](/concepts/governance/network-roles)) * **Data** — every furnished record carries a network reference, so the network acts as a boundary on what data can be read * **Rules** — the [querying policies](/concepts/governance/querying-policies) and [furnishing policies](/concepts/governance/furnishing-policies) that govern each product, and how data is allowed to move between this network and neighbouring networks (see [Network Governance](/concepts/governance/network-governance)) A network has a name, an optional description, and a single **governor entity** that owns it. ## The network tree Networks are not flat. Every network has at most one **parent network**, so networks form a **tree** where smaller, more specific networks live underneath broader ones. A network with no parent sits at the top of the tree and is called a **root** network. Networks below it are its **descendants**; the networks directly under it are its **children**. ```mermaid theme={null} graph TD Federation[Federation Root] BankA[Coastal Bank] BankB[Summit Bank] BankASub1[Coastal — Credit Cards] BankASub2[Coastal — Mortgages] BankBSub1[Summit — Auto Loans] Federation --> BankA Federation --> BankB BankA --> BankASub1 BankA --> BankASub2 BankB --> BankBSub1 ``` In the diagram above, *Coastal Bank* is a child of *Federation Root* and a parent of *Coastal — Credit Cards*. *Summit Bank* is a **sibling** of *Coastal Bank* because they share a parent. *Coastal — Credit Cards* and *Summit — Auto Loans* are **cousins** because they share a common ancestor (the Federation) without being in each other's direct line. This lets a single arrangement — such as a sponsor bank and the fintechs it sponsors — share a parent network while keeping each participant's data within its own boundary. ## Relations between networks These are the words SOLO uses to describe how any two networks relate. They show up throughout the permissioning model, so it's worth getting them straight up front. | Term | Meaning | | - | - | | **Parent** | The network directly above this one. | | **Child** | A network whose parent is this one. | | **Ancestor** | Any network reachable by walking upward — parent, grandparent, and so on, up to the root. | | **Descendant** | Any network reachable by walking downward — child, grandchild, and so on. | | **Sibling** | Two networks that share the same parent. | | **Cousin** | Two networks that share a common ancestor but are not in each other's ancestor or descendant line. | | **Root** | A network with no parent. | > [!NOTE] > > SOLO treats siblings as a special case of cousins for permissioning purposes. > Anywhere governance rules talk about cousins, siblings are included. ## Kinds of network Every network carries a `network_type` describing its place in an arrangement: **standard** networks are the default, while **sponsor** and **partner** networks describe a sponsor bank and the fintechs it sponsors, nested under it in the tree. All kinds behave the same way — they can have their own participants, their own data, and their own governance settings. When you furnish, the upload targets a network directly, and that network's mapping assignments determine how the data is accepted and routed (see [Furnishing Policies](/concepts/governance/furnishing-policies)). > [!TIP] > > If you operate a single product line, you probably only need one network. Child > networks become useful when you want to keep data, participants, or rules > isolated between distinct offerings under the same operator. ## Network lifecycle Networks are created deliberately, through the **SOLO Dashboard**, as part of onboarding your organization or standing up a new arrangement (a consortium, a sponsorship structure, a new line of business). A new network needs: * a **name** and an optional **description**, * an optional **parent network**, which places it in the tree, and * a **governor entity** — the entity that owns and administers it. The entity that creates the network is recorded as its governor entity, and SOLO automatically provisions that entity's **governor membership** on the new network at the same time. This means a freshly created network is never ownerless: from the first moment, exactly one entity can administer it, add participants, and configure its [governance rules](/concepts/governance/network-governance). After creation, the governor typically: 1. adds participants with the [roles](/concepts/governance/network-roles) they need (furnisher, querier, or additional governors), 2. creates and configures the [querying policies](/concepts/governance/querying-policies) and [furnishing policies](/concepts/governance/furnishing-policies) for the products the network will use, and 3. optionally enables governance rules to open query paths to related networks. ## Permissions & roles Your organization joins a network with one or more **roles**. Roles describe what you can do, not what data you can see. | Role | What it allows | | - | - | | **Governor** | Administer the network — configure policies and directory metadata. A governor seat is **not** a backdoor to other members' data. | | **Furnisher** | Contribute data into the network for a product. | | **Querier** | Run product queries scoped to the network. | A single organization can hold several roles at once (for example, both furnisher and querier). See [Network Roles](/concepts/governance/network-roles) for the full model. > [!WARNING] > > Governing a network does **not** automatically let you read what other members > have furnished. Reading another participant's data still requires > [consent](/concepts/identity/consent) and [entitlement](/concepts/governance/entitlement). Roles > grant *capabilities*, not *visibility*. ## Policy usage within a network A network ties products to participants through policies: * A **[querying policy](/concepts/governance/querying-policies)** binds a product to the network and defines, field by field, what queriers may read. * A **[furnishing policy](/concepts/governance/furnishing-policies)** defines how one type of furnished record is mapped, validated, and persisted. When you query, you scope the request to one or more network ids and a querying policy, so the network knows both *where* to read and *which* reading rules apply. When you furnish a file, each record's `_type` routes it to the network's furnishing policies that claim that type — you never pass a furnishing policy id yourself. ```mermaid theme={null} flowchart LR M[Your organization] -->|querier| QP[Querying policy] M -->|furnisher| FP[Furnishing policy] QP --> Prod[Product] FP --> Prod Prod --> Net[(Network data)] ``` ## Joining a network Membership and roles are arranged with your SOLO account manager. Once you're a member, you can: * **search** for entities within the network, * **furnish** products into it (as a furnisher), and * **query** products from it (as a querier, with [consent](/concepts/identity/consent)). See the [quickstart](/home/quickstart/join-a-network) for a walkthrough. ## In the dashboard ![Create network — review and submit](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/networks/network-complete/01-create-dialog-filled.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=e243396106a6ebad335329abb2ccbb75) ![Network created — detail page with new network](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/networks/network-complete/02-create-success-detail.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=2eab76203708081b33499734e5ac3771) ![Networks list — newly created network visible](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/networks/network-complete/05-create-success-list.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=e40a3e70913460ac01abf180cb32a499) ![Topbar network multiselect open](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/network-scope/network-complete/06-selector-open.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=2957930ead6c83adc1dcdc6558e4b561) ## Related concepts > **[Network Roles](/concepts/governance/network-roles)** > > How entities participate in a network. > **[Network Governance](/concepts/governance/network-governance)** > > How data can flow between related networks. > **[Querying Policies](/concepts/governance/querying-policies)** > > The field-level read rules a network applies to each product. > **[Furnishing Policies](/concepts/governance/furnishing-policies)** > > How furnished data is accepted, validated, and routed. > **[Entitlement](/concepts/governance/entitlement)** > > Why network membership alone doesn't grant data access. # Querying Policies Source: https://docs.solo.one/concepts/governance/querying-policies Field-by-field read rules that bind a product to a network A **querying policy** is the set of read rules a [network](/concepts/governance/networks) applies to a [product](/api-overview/querying/products). It binds the product to the network and defines, field by field, what queriers may read — and from which sources. The same product can be configured differently in different networks. Querying policies are how a network tailors a shared product schema to its own agreements: expose these fields, suppress those, prefer this furnisher's data over that one's. ```mermaid theme={null} flowchart LR Prod[Product] --- NP[Network-product association] Net[Network] --- NP NP --- Pol[Querying policy] Pol --> Models[Per-model selections] Models --> Fields[Per-field rules:
enabled, choice, provenance order] ``` > [!NOTE] > > Querying policies govern *reads*. The write-side counterpart — how furnished > data is accepted, validated, and routed — is the > [furnishing policy](/concepts/governance/furnishing-policies). ## Why querying policies exist Products define a standard schema — every KYC certificate has the same shape everywhere. But networks have different agreements about what may be shared: a closed consortium might expose document-verification outcomes that a broader federation suppresses; one network might accept any furnisher's biometric data while another only trusts a named source. Querying policies let a network: * expose only the fields appropriate for its participants, * rank which **sources** (provenances) a field may be answered from, in order of preference, * keep read rules explicit, versionable, and auditable, and * evolve those rules without changing the underlying product or any integration code — queriers keep calling the same endpoint. Because the policy hangs off the product-network association rather than the product itself, the same product can run under strict rules in one network and permissive rules in a sibling network, simultaneously. ## What a policy contains A querying policy hangs off the **network-product association** (the pairing of one product with one network) and carries: | Attribute | Meaning | | - | - | | `name` | Display name, unique per product-network pairing for its author. | | `is_default` | Whether this is the policy used when a query doesn't name one explicitly. | | `enforced` | Whether the policy's selections are applied to reads. | | `status` | Lifecycle state: `draft`, `published`, or `archived`. | Beneath the policy sit **per-model selections** — one for each product model the policy has an opinion about — and beneath those, **per-field rules**: * **`is_enabled`** — whether the field may be read at all under this policy. * **`boolean_choice`** — for fields with a constrained choice, which value the policy requires. * **`selected_provenance_option_ids`** — an *ordered* list of acceptable data sources for the field. Order matters: the first option is tried first, and later options are lower priority. A model with no selections is explicitly excluded from provenance customization; a model the policy doesn't mention at all falls back to the product's defaults. ## Lifecycle Policies carry a `status` of `draft`, `published`, or `archived`. New revisions are authored as drafts (the dashboard's version wizard works this way); saving a configuration through the API leaves the policy in `published` status, at which point it governs live queries. Archived policies are retained for audit but no longer selectable. > [!WARNING] > > Saving a configuration **replaces** the policy's per-model and per-field > selections with the submitted set — it is not a patch. Always submit the > complete intended configuration. ## Creating a policy Creating a querying policy is a two-step flow: create the policy shell, then configure its field selections. ### Step 1 — create `POST /v1/networks/policies/querying` with the product, the network, and a name. The server resolves the product-network association — creating it if it doesn't exist yet — and returns the new policy. ```bash theme={null} curl -X POST https://api.solo.one/v1/networks/policies/querying \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "product_id": "1b9a4c33-…", "network_id": "9f1c0c2e-…", "name": "Coastal KYC — standard read" }' ``` ```json theme={null} { "id": "5e7d2a14-…", "network_product_id": "c4f8e6d1-…", "entity_id": "7c2d91e4-…", "name": "Coastal KYC — standard read", "is_default": false } ``` ### Step 2 — configure fields `PUT /v1/networks/policies/querying/{policy_id}/configuration` with the full set of per-model, per-field selections: ```bash theme={null} curl -X PUT https://api.solo.one/v1/networks/policies/querying/5e7d2a14-…/configuration \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "model_selections": [ { "product_model_definition_id": "0a1b2c3d-…", "field_selections": [ { "product_field_definition_id": "e5f6a7b8-…", "is_enabled": true, "boolean_choice": null, "selected_provenance_option_ids": ["11aa22bb-…", "33cc44dd-…"] }, { "product_field_definition_id": "f9e8d7c6-…", "is_enabled": false, "boolean_choice": null, "selected_provenance_option_ids": [] } ] } ] }' ``` ```json theme={null} { "id": "5e7d2a14-…", "name": "Coastal KYC — standard read", "status": "published" } ``` You can optionally include a `"name"` in the configuration body to rename the policy in the same call. > [!NOTE] > > Policy authoring is a [governor](/concepts/governance/network-roles) > responsibility. Queriers consume policies; they don't write them. ## How a query selects a policy When you [query a product](/api-overview/querying/overview), the request body carries the network scope and, optionally, the policy: ```bash theme={null} curl -X POST https://api.solo.one/v1/products/kyc_certificate_snapshot/query \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "consent_id": "a3f0b9c7-…", "network_ids": ["9f1c0c2e-…"], "policy_id": "5e7d2a14-…" }' ``` * **`network_ids`** (required) — one or more networks to read across. * **`policy_id`** (optional) — the querying policy to apply. The single policy is applied across every network in `network_ids`. **If omitted, each network's default policy** (`is_default: true`) **is used.** The response echoes the resolution per network, so you always know which rules shaped each result: ```json theme={null} { "results": [ { "meta": { "network_id": "9f1c0c2e-…", "policy_id": "5e7d2a14-…" }, "…": "…" } ] } ``` ### Resolution and audit stamping Every query event is stamped with the querying policy that governed it, so results are auditable after the fact. Resolution works through a fixed fallback chain: 1. the policy id **explicitly stamped** on the query event; 2. failing that, a policy id found in the **request or response payload**; 3. failing that, the policy referenced by **certificates linked** to the query; 4. failing that, the **network's default policy** — matched first against the querying entity's own policies, then against the network governor's. As a caller you only ever interact with step 1 (pass `policy_id`) or step 4 (omit it and rely on the default). Steps 2–3 are how SOLO back-fills the audit trail for historical events. > [!TIP] > > If your network has exactly one published policy per product and it's marked > default, queriers can omit `policy_id` entirely and always get the right > rules. Reserve explicit `policy_id` for networks that run multiple read > profiles side by side. ### Querying across multiple networks `network_ids` accepts more than one network, and the listed `policy_id` (or each network's default) is applied across all of them. Two things to keep in mind when fanning a query out: * **Authorization is per network.** Each network in the list is checked independently against your memberships and the [governance rules](/concepts/governance/network-governance). A network you can't scope to fails the query with an authorization error rather than being silently dropped. * **The policy still only caps; it doesn't fetch.** Adding networks widens where SOLO looks, but [entitlement](/concepts/governance/entitlement) still filters every record to what you've earned, regardless of how many networks the query touches. ## The policy is a ceiling, not a grant A querying policy defines what the product **may** expose in the network — for anyone. What *you* get back is further narrowed by [entitlement](/concepts/governance/entitlement): | Question | Answered by | | - | - | | *What may be read from this network, at most?* | **Querying policy** | | *What may* ***I*** *read, given my history with this subject?* | **Entitlement** | A query returns the **intersection**: fields the policy permits **and** records you are entitled to. Enabling a field in the policy does not push data to anyone who hasn't earned it; disabling a field hides it even from participants who furnished it through this product in this network. ## Errors you may encounter All errors use the standard envelope (see [Errors](/home/errors)): ```json theme={null} { "detail": "…", "error_code": "…" } ``` | Situation | Status / code | | - | - | | Configuration references an unknown `policy_id` | `404` `RESOURCE_NOT_FOUND` | | Creating a duplicate policy (same product-network pairing, same name) | `409` `RESOURCE_CONFLICT` | | Malformed selection payload (missing field, bad UUID) | `422` (short shape, no `error_code`) | | Querying a network you can't scope to | `403` `PERMISSION_DENIED` | ## In the dashboard ![Querying policies list](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/policies/policies-and-entities-complete/01-querying-list.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=af91c02c886f7026ede930802f2bffb8) ![Create querying policy — ready to submit](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/policies/policies-and-entities-complete/02-querying-create-dialog-filled.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=b7d2c5a2e7e8a501d078cbb14d32aeb4) ![Querying policy created — detail page](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/policies/policies-and-entities-complete/03-querying-create-success-detail.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=292107ec4142d528b61070949c42e964) ![Querying policy field configuration — edit mode](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/policies/policies-and-entities-complete/04-querying-wizard-fields.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=02b6b1b3817d4f3771b6ede725ea92a1) ![KYC product detail — Querying Policies tab](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/16-product-detail-querying-policies.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=ccaea0c9cc24e25461fdf8e0b9f1a963) ![Run query opened from policy detail](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/policies-and-entities-complete/05-from-policy-detail.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=b4aba5916d4522613c36304a261d53ce) ![Run query — choose entity](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/policies-and-entities-complete/06-from-policy-entity.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=646bcebd81833323d7f2e776bb8b612b) ![Run query — Network & Furnishers tab](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/policies-and-entities-complete/07-from-policy-networks-tab.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=f33068c7241e82d3570e3cde0bac6426) ![Query completed — run detail overview](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/09-wizard-success-detail.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=cfccc007fd2f06d5eb61365bdbd2888a) ## Related concepts > **[Furnishing Policies](/concepts/governance/furnishing-policies)** > > The write-side counterpart: how data gets into the network. > **[Entitlement](/concepts/governance/entitlement)** > > The per-participant layer that narrows what a policy exposes. > **[Querying](/api-overview/querying/overview)** > > How policies plug into a product query. > **[Networks](/concepts/governance/networks)** > > The boundary policies are scoped to. # Consent Source: https://docs.solo.one/concepts/identity/consent Recording permission to query a consumer or business **Consent** is your record that you have permission to query a specific consumer or business. Querying personal and business data carries a legal obligation to have a permissible purpose — consent is how the network captures, and can later prove, that you had one. A consent record is two things at once: * **A key.** You create it once per subject per network, receive a `consent_id`, and pass that `consent_id` on every product query for the subject. Without a valid `consent_id`, consumer and business queries are rejected. * **An audit artifact.** The record permanently captures *who* consented, *as identified by which details*, *when*, and *with what scope* — so that every query you ever ran can be traced back to a lawful basis. That dual nature drives the API's design, including its strictest rule: the identity on a consent record can never be edited. ## Lifecycle A consumer consent record moves through a simple lifecycle, with one endpoint for each transition you control: ```mermaid theme={null} flowchart LR A[Gather permission
from the subject] --> B["Create
POST /v1/consent/consumer_identity"] B --> C[consent_id] C --> D["Query products
POST /v1/products/…/query"] C --> E["Read back
GET /v1/consent/consumer_identity/{consent_id}"] C --> F["Update scope / expiry / fields
PUT /v1/consent/consumer_identity/{consent_id}"] F --> D F --> G[Expired
per expires_at] ``` You gather the subject's permission **outside** the API — in your onboarding flow, your terms, your call script — then record it. From there the `consent_id` is reusable on queries until the consent expires, and you can read it back or adjust its mutable attributes at any time. ## What a consent record contains All three consumer endpoints return the same shape: | Field | Type | Notes | | - | - | - | | `consent_id` | string | The token you pass on product queries | | `field_access_grants` | array | Grants attached to this consent — see [below](#field-access-grants) | | `created_at` | string | When the record was created | | `scope` | string | Free-form scope label; empty until you set one | | `expires_at` | string \| null | When the consent lapses; `null` means no expiry recorded | | `consented_fields` | string \| null | Free-form description of the fields the subject consented to | | `events` | string | Event history for the record | ## Creating consent `POST /v1/consent/consumer_identity` records consent server-to-server. You supply the subject's identity and attest that you gathered their permission beforehand. | Body field | Required | Notes | | - | - | - | | `network_id` | Yes | The [network](/concepts/governance/networks) this consent belongs to | | `did_gather_consent_from_consumer_prior` | Yes | Must be `true` — see warning below | | `consumer_consent_event_identity` | Yes | `first_name`, `last_name`, `date_of_birth`, `personal_email`, `phone_number`, `social_security_number` | | `consumer_identity_id` | No | Link to an existing consumer profile found via [entity search](/concepts/identity/entities) | ```bash theme={null} curl -X POST https://api.solo.one/v1/consent/consumer_identity \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "network_id": "9f1c0c2e-…", "did_gather_consent_from_consumer_prior": true, "consumer_consent_event_identity": { "first_name": "Jane", "last_name": "Doe", "date_of_birth": "1990-01-15", "personal_email": "jane@example.com", "phone_number": "+14155550100", "social_security_number": "123-45-6789" } }' ``` A `200 OK` returns the full record, including a default field access grant created alongside the consent: ```json theme={null} { "consent_id": "a3f0b9c7-…", "field_access_grants": [ { "furnishing_entity_id": null, "field_definitions": [ "fraud_verification_event.confirmed_fraud_indicator", "fraud_verification_event.fraud_attribute_label", "fraud_verification_event.fraud_event_date" ], "effective_from": "2026-06-09", "effective_to": null } ], "created_at": "2026-06-09T22:14:03.512000+00:00", "events": "", "scope": "", "expires_at": null, "consented_fields": null } ``` > [!WARNING] > > `did_gather_consent_from_consumer_prior: true` is an **attestation** that you > obtained the consumer's permission before making this call. Sending `false` > is rejected with `400 VALIDATION_ERROR` — the network will not create a > consent record that asserts no consent was gathered. ### Linking identity: `consumer_identity_id` vs. the identity payload How the consent attaches to an [entity](/concepts/identity/entities) depends on whether you pass `consumer_identity_id`: * **With `consumer_identity_id`** — the consent links to that existing consumer profile. Use this after a successful `GET /v1/entities/consumer_identity/search`, so the subject's history stays consolidated on one profile. * **Without `consumer_identity_id`** — a new consumer profile is created from the identity payload and linked to the consent. Either way, the identity details you submitted are stamped onto the consent record itself as the audit trail of who consented. ## Using a consent ID Pass the `consent_id` in the body of a product query. The network resolves the consent to the subject's identity and applies the appropriate field access — you don't repeat the subject's identifying details on every call. ```bash theme={null} curl -X POST https://api.solo.one/v1/products/kyc_certificate_snapshot/query \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "consent_id": "a3f0b9c7-…", "network_policy": [ { "network_id": "9f1c0c2e-…", "policy_id": "5e7d2a14-…" } ] }' ``` See [Querying your first product](/home/quickstart/query-a-product) for the end-to-end walkthrough. ## Reading consent back `GET /v1/consent/consumer_identity/{consent_id}` returns the current state of a consent record. The `network_id` query parameter is **required** — the lookup is scoped to that network: ```bash theme={null} curl -G https://api.solo.one/v1/consent/consumer_identity/a3f0b9c7-… \ -H "Authorization: Bearer $SOLO_TOKEN" \ --data-urlencode "network_id=9f1c0c2e-…" ``` The response is the same shape as create. If the `consent_id` doesn't exist **or** exists in a different network, you get a `404`: ```json theme={null} { "detail": "Consent record not found", "error_code": "RESOURCE_NOT_FOUND" } ``` > [!NOTE] > > A consent record belongs to one network. The same subject needs a separate > consent in each network you query them through, and a `consent_id` minted in > one network is invisible — `404`, not `403` — from another. ## Updating consent `PUT /v1/consent/consumer_identity/{consent_id}` changes the **mutable attributes** of a consent record: its scope, its expiry, and the description of consented fields. | Body field | Required | Notes | | - | - | - | | `network_id` | Yes | Network scope — same rule as read | | `scope` | At least one of these three | New scope label | | `expires_at` | | New expiry timestamp (ISO 8601) | | `consented_fields` | | New consented-fields description | ```bash theme={null} curl -X PUT https://api.solo.one/v1/consent/consumer_identity/a3f0b9c7-… \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "network_id": "9f1c0c2e-…", "scope": "kyc", "expires_at": "2027-06-09T00:00:00Z" }' ``` The response is the **post-update** record: ```json theme={null} { "consent_id": "a3f0b9c7-…", "field_access_grants": [ { "furnishing_entity_id": null, "field_definitions": [ "fraud_verification_event.confirmed_fraud_indicator", "fraud_verification_event.fraud_attribute_label", "fraud_verification_event.fraud_event_date" ], "effective_from": "2026-06-09", "effective_to": null } ], "created_at": "2026-06-09T22:14:03.512000+00:00", "events": "", "scope": "kyc", "expires_at": "2027-06-09T00:00:00+00:00", "consented_fields": null } ``` Omitting all three updatable fields is a `400`: ```json theme={null} { "detail": "At least one of scope, expires_at, or consented_fields must be provided", "error_code": "VALIDATION_ERROR" } ``` ### Identity is immutable — by design The update endpoint deliberately accepts **only** `scope`, `expires_at`, and `consented_fields`. The subject's name, date of birth, email, phone, and SSN on a consent record can never change. > [!WARNING] > > A consent record is an audit artifact of **who consented and when**. If the > identity could be edited after the fact, the record could no longer prove > which person's permission authorized your past queries. To consent with > different identity data — a legal name change, a corrected SSN — create a > **new** consent record and use its `consent_id` going forward. ## Field access grants Each consent record carries one or more **field access grants** in `field_access_grants`. A grant is a concrete statement of *which fields, from which furnisher, over which time window* this consent unlocks. | Grant field | Type | Meaning | | - | - | - | | `furnishing_entity_id` | string \| null | The furnisher whose contributed data this grant covers. `null` means the grant is not pinned to a specific furnisher. | | `field_definitions` | array of strings | The fields covered, as `source_table.source_column` strings — e.g. `fraud_verification_event.fraud_event_date`. | | `effective_from` | string \| null | Date the grant becomes active. | | `effective_to` | string \| null | Date the grant lapses; `null` means open-ended. | When you create a consent, a default grant is attached automatically with `effective_from` set to the creation date and a baseline set of field definitions. Grants outside their effective window no longer apply. > [!NOTE] > > Grants describe what this **consent** covers. They sit alongside — not in > place of — [entitlement](/concepts/governance/entitlement): a field appears in a query > result only if the consent covers asking, the network's > [querying policy](/concepts/governance/querying-policies) exposes the field, and you have > earned entitlement to it. ## Failure modes | Status | `error_code` | When | Example `detail` | | - | - | - | - | | `400` | `VALIDATION_ERROR` | `did_gather_consent_from_consumer_prior` is `false` on create | `Consent cannot be created without prior consumer consent gathering` | | `400` | `VALIDATION_ERROR` | Update with none of `scope`, `expires_at`, `consented_fields` | `At least one of scope, expires_at, or consented_fields must be provided` | | `404` | `RESOURCE_NOT_FOUND` | Unknown `consent_id`, or a consent from a different network | `Consent record not found` | | `422` | — | Request shape errors: missing required fields, wrong types | `body -> consumer_consent_event_identity -> date_of_birth: value is not a valid datetime` | Error responses follow the standard envelope — see [Errors](/home/errors): ```json theme={null} { "detail": "Consent cannot be created without prior consumer consent gathering", "error_code": "VALIDATION_ERROR" } ``` ## Business consent `POST /v1/consent/business_identity` records consent to query a business, mirroring the consumer flow: ```bash theme={null} curl -X POST https://api.solo.one/v1/consent/business_identity \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "did_gather_consent_from_business_prior": true, "business_consent_event_identity": { "business_legal_name": "Acme Corp", "business_jurisdiction_of_formation": "DE", "business_registration_identifier_from_jurisdiction_of_formation": 1234567, "business_tax_identifier_type": "EIN", "business_tax_identifier_value": "12-3456789" }, "identity": { "business_legal_name": "Acme Corp", "business_jurisdiction_of_formation": "DE", "business_registration_identifier_from_jurisdiction_of_formation": 1234567 } }' ``` The response contains `consent_id`, `consented_fields`, `created_at`, `events`, and `scope`. Use the `consent_id` on business product queries exactly as you would a consumer one. > [!NOTE] > > Business consent currently supports **create only**. There is no > `GET` or `PUT /v1/consent/business_identity/{consent_id}` today — read-back and > updates are consumer-only. If you need to change a business consent's > attributes, create a new record and switch to its `consent_id`. ## How consent relates to entitlement Consent and [entitlement](/concepts/governance/entitlement) are complementary gates, and a query needs both: | | Consent | Entitlement | | - | - | - | | Question it answers | *May I query this subject at all?* | *Which fields come back?* | | How you get it | Record permission, get a `consent_id` | Earn it by furnishing or querying the subject | | Scope | One subject, in one network | Per field, per entity | | Carried on the query | `consent_id` in the request body | Applied automatically by the network | | Can it change? | Mutable scope/expiry; immutable identity | Accrues with each furnish and query | Consent without entitlement yields a permitted query with little or no data; entitlement without consent yields no query at all. ## Common questions > **Should I reuse a consent\_id across queries?** > > Yes. Create one consent per subject per network and reuse its `consent_id` > on every product query for that subject while the consent is valid. You do > not need a new consent record for each query. > **Why does GET return 404 for a consent I just created?** > > The read is scoped by the `network_id` query parameter. The most common > cause is passing a different `network_id` than the one the consent was > created under — the record exists, but it isn't visible from that network, > so the API reports `404 RESOURCE_NOT_FOUND` rather than confirming it > exists elsewhere. > **When do I update versus create a new consent?** > > **Update** when the subject's permission itself changed shape — a broader > or narrower scope, a new expiry date, a revised description of consented > fields. **Create new** when anything about the subject's identity needs to > differ: identity fields are immutable, so a corrected SSN, a legal name > change, or a new email all require a fresh record and a switch to its > `consent_id`. > **Does furnishing require consent?** > > The `consent_id` requirement documented here applies to **queries** — > reading data back out of the network. See > [Furnishing](/api-overview/furnishing/overview) for the obligations that apply when > contributing data. ## In the dashboard ![Consumer — consent tab](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/entities/policies-and-entities-complete/22-consumer-tab-consent.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=fe1a753eaf1f191521b7ae30231a5aaf) ![Find or create consumer dialog](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/entities/policies-and-entities-complete/18-consumer-find-or-create-dialog.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=dcfa19340f179d3444b6044e87d4a255) ![Find or create consumer — ready to submit](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/entities/policies-and-entities-complete/19-consumer-find-or-create-filled.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=1d2a94fb121e4e0e7a02bad4394f06bc) ![Find or create business dialog](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/entities/policies-and-entities-complete/25-business-find-or-create-dialog.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=0b01f87536e85e0d9947924541120747) ![Find or create business — ready to submit](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/entities/policies-and-entities-complete/26-business-find-or-create-filled.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=20c87cc5a7653fbe6e5c64d320ef7238) ![Business — consent tab](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/entities/policies-and-entities-complete/28-business-tab-consent.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=c111540b738b851f2a700f22d1f4406c) ## Related concepts > **[Entities](/concepts/identity/entities)** > > The subjects consent records are about, and how to find them. > **[Querying](/api-overview/querying/overview)** > > How consent plugs into a product query. > **[Entitlement](/concepts/governance/entitlement)** > > What determines which fields a query returns. > **[Errors](/home/errors)** > > The error envelope and status codes used across the API. # Entities Source: https://docs.solo.one/concepts/identity/entities Consumers and businesses — the subjects every query and furnish is about An **entity** is the subject of a verification: the person or organization you are furnishing data about or querying data for. Every furnish and every query in the SOLO Network is about exactly one entity — you never operate on data in the abstract, only in the context of a specific consumer or business. > **Consumers** > > Individual people, identified by personal details such as name, date of birth, email, phone, and SSN. > **Businesses** > > Legal entities, identified by details such as legal name, jurisdiction of formation, registration number, and tax identifier. The same person or company resolves to a **single profile** in a network. When you furnish or query, the network matches the identity you supply to that profile, so contributions from different participants accumulate on one subject rather than fragmenting into duplicates. ## How entities come into existence You never call a "create entity" endpoint. Profiles materialize as a side effect of normal participation: **1. Furnishing data** When you [furnish](/api-overview/furnishing/overview) through a product endpoint (for example `POST /v1/products/kyc_certificate_snapshot/furnish`), the identity in your payload is resolved to an existing profile or a new one is created. **2. Creating consent** When you create a [consent](/concepts/identity/consent) record with `POST /v1/consent/consumer_identity`, the identity payload is used to create and link a consumer profile if you don't point at an existing one. **3. Linking an existing profile** Consent creation accepts an optional `consumer_identity_id`. If you've already found the subject via search, pass its `id` and the consent links to that profile instead of creating a new one. ## Consumers A consumer represents a single individual, identified by: | Field | Notes | | - | - | | `first_name`, `last_name` | Legal name | | `date_of_birth` | ISO `YYYY-MM-DD` | | `personal_email` | | | `phone_number` | | | `social_security_number` | Used for high-confidence matching | > [!TIP] > > You don't need every field to identify a consumer, but more identifiers produce > higher-confidence matches. SSN and date of birth are the strongest signals. ## Businesses A business represents a legal entity, identified by: | Field | Notes | | - | - | | `business_legal_name` | Registered legal name | | `business_jurisdiction_of_formation` | e.g. a US state | | `business_registration_identifier_from_jurisdiction_of_formation` | Registration number issued at formation | | `business_tax_identifier_type` / `business_tax_identifier_value` | e.g. `EIN` | | `business_dba_name` | Optional "doing business as" name | | `business_website_url` | Optional | ## Searching for entities Before furnishing or querying, look up subjects that already exist with the search endpoints: * `GET /v1/entities/consumer_identity/search` * `GET /v1/entities/business_identity/search` Both endpoints share the same mechanics: * **`network_id` is required.** Search is always scoped to a single [network](/concepts/governance/networks); you cannot search across networks in one call. * **All identity parameters are optional** and combine with AND — every criterion you supply must match. * **`limit`** caps results between 1 and 100, defaulting to 20. ### Match semantics Each search parameter has a fixed matching rule: | Kind | Parameter | Match | | - | - | - | | Consumer | `first_name` | Case-insensitive partial | | Consumer | `last_name` | Case-insensitive partial | | Consumer | `personal_email` | Case-insensitive partial | | Consumer | `social_security_number` | Exact | | Consumer | `date_of_birth` | Exact (`YYYY-MM-DD`) | | Business | `business_legal_name` | Case-insensitive partial | | Business | `business_dba_name` | Case-insensitive partial | | Business | `business_email` | Case-insensitive partial | | Business | `federal_ein` | Exact (integer) | Partial matching means `first_name=jan` matches both "Jan" and "Janet"; exact-match fields like SSN and EIN must be supplied in full. ### Searching consumers ```bash theme={null} curl -G https://api.solo.one/v1/entities/consumer_identity/search \ -H "Authorization: Bearer $SOLO_TOKEN" \ --data-urlencode "network_id=9f1c0c2e-…" \ --data-urlencode "first_name=Jane" \ --data-urlencode "last_name=Doe" \ --data-urlencode "limit=10" ``` A `200 OK` returns an array of matching profiles: ```json theme={null} [ { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "identifier": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "created_at": "2026-05-02T18:21:44.310000+00:00", "updated_at": "2026-06-01T09:03:12.778000+00:00", "first_name": "Jane", "last_name": "Doe", "personal_email": "jane@example.com", "date_of_birth": "1990-01-15", "social_security_number": "123-45-6789" } ] ``` Consumer results carry these fields: | Field | Type | Notes | | - | - | - | | `id` | UUID | The consumer profile's identifier | | `identifier` | string | Same value as `id` | | `created_at` | string \| null | When the profile was created | | `updated_at` | string \| null | When the profile last changed | | `first_name` | string \| null | | | `last_name` | string \| null | | | `personal_email` | string \| null | | | `date_of_birth` | string \| null | `YYYY-MM-DD` | | `social_security_number` | string \| null | | ### Searching businesses ```bash theme={null} curl -G https://api.solo.one/v1/entities/business_identity/search \ -H "Authorization: Bearer $SOLO_TOKEN" \ --data-urlencode "network_id=9f1c0c2e-…" \ --data-urlencode "business_legal_name=Acme" \ --data-urlencode "limit=10" ``` ```json theme={null} [ { "id": "b4d2e9a1-0f6c-4a3e-8d57-91c2f0ab34de", "identifier": "b4d2e9a1-0f6c-4a3e-8d57-91c2f0ab34de", "created_at": "2026-04-18T11:02:09.114000+00:00", "updated_at": "2026-05-30T16:47:51.902000+00:00", "business_legal_name": "Acme Corp", "business_dba_name": "Acme", "business_email": "ops@acme.example.com", "business_entity_type": "corporation", "business_date_of_incorporation": "2015-03-09", "business_phone": "+14155550123", "business_website_url": "https://acme.example.com", "federal_ein": 123456789, "employee_count": 250 } ] ``` Business results carry these fields: | Field | Type | Notes | | - | - | - | | `id` | UUID | The business profile's identifier | | `identifier` | string | Same value as `id` | | `created_at` | string \| null | When the profile was created | | `updated_at` | string \| null | When the profile last changed | | `business_legal_name` | string \| null | | | `business_dba_name` | string \| null | | | `business_email` | string \| null | | | `business_entity_type` | string \| null | e.g. corporation, LLC | | `business_date_of_incorporation` | string \| null | | | `business_phone` | string \| null | | | `business_website_url` | string \| null | | | `federal_ein` | integer \| null | | | `employee_count` | number \| null | | > [!NOTE] > > Search returns **core identity fields only** — enough to confirm you have the > right subject. It is not a data product: credit and verification data come > back through [product queries](/api-overview/querying/overview), gated by consent and > entitlement. ## From search to consent: find or create The search endpoints exist primarily to support a **find-or-create** workflow before consenting and querying a consumer: **1. Search first** Call `GET /v1/entities/consumer_identity/search` with the strongest identifiers you hold (SSN and date of birth where possible). **2. Found a match? Link it** Pass the result's `id` as `consumer_identity_id` when calling `POST /v1/consent/consumer_identity`. The consent attaches to the existing profile, keeping the subject's history consolidated. **3. No match? Let consent create the profile** Omit `consumer_identity_id`. The identity payload on the consent request creates and links a fresh consumer profile. **4. Query with the consent\_id** Use the returned `consent_id` on [product queries](/api-overview/querying/products) for that subject. See [Consent](/concepts/identity/consent) for the full consent API. ## Provenance: one entity, many furnishers An entity profile is a **shared subject**, not a record you own. In a healthy network, several participants typically furnish data about the same consumer or business — one bank contributes identity verification outcomes, another contributes fraud events, a third contributes business registration details. All of it accrues to the same profile. Two consequences follow: 1. **You don't control the whole profile.** Your furnished fields sit alongside fields contributed by other participants. Updating your own contribution never overwrites theirs. 2. **What you can read is governed, not automatic.** Sharing a profile with another furnisher does not mean you see their data. A query returns the intersection of what the network's [querying policy](/concepts/governance/querying-policies) exposes and what you are [entitled](/concepts/governance/entitlement) to — entitlement you earn by furnishing or previously querying that entity. > [!NOTE] > > Identifying an entity tells the network *who* you mean. Consent and entitlement > determine *what* you may read about them. The same search result can yield > very different query responses for two different participants. For how each furnisher's contribution stays attributed — and how the network chooses which furnisher's verification to reuse — see [Provenance](/concepts/trust/provenance). ## Network scoping Entities are always handled in the context of a network: * Search requires a `network_id` and only surfaces subjects visible to you in that network. * Consent records are created against a `network_id`, and the same subject needs separate consent per network. * Furnishes and queries run under a network's policies. If you participate in multiple networks, treat each as a separate scope — an entity you can see in one network is not automatically visible in another. See [Networks](/concepts/governance/networks) for how membership and roles work. ## Common questions > **My search returned an empty array — what does that mean?** > > No profile visible to you in that network matched **all** of your criteria. > Remember that criteria combine with AND: `first_name=Jane` plus > `last_name=Smith` only matches profiles satisfying both. Try fewer or > looser criteria, or double-check exact-match fields (`social_security_number`, > `date_of_birth`, `federal_ein`) for typos — partial input never matches an > exact-match field. > **What if I get multiple matches?** > > Partial matching on names and emails can surface several candidates. > Disambiguate with the exact-match identifiers — SSN or date of birth for > consumers, federal EIN for businesses — before linking a profile to a > consent record. When in doubt, omit `consumer_identity_id` and let consent creation > work from the identity payload you trust. > **Can I list entities without any criteria?** > > Yes. All identity parameters are optional, so a request with only > `network_id` returns profiles up to `limit` (default 20, max 100). This is > useful for an initial look at a network, but for production matching always > pass the strongest identifiers you hold. ## In the dashboard ![Entities hub — All tab](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/entities/policies-and-entities-complete/14-hub-all.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=a89554c89a74e5ae3003f694a2b0463f) ![Entities hub — Consumers tab](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/entities/policies-and-entities-complete/15-hub-consumers.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=e9a34ddaa3c24fb48eab82f621bac505) ![Entities hub — Businesses tab](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/entities/policies-and-entities-complete/16-hub-businesses.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=287f2c1ee07dc82aa7911ca43f335744) ![Consumers list](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/entities/policies-and-entities-complete/17-consumers-list.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=f6e5e3948d98fdff5de35fc51dc0feee) ![Consumer consent created — profile detail](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/entities/policies-and-entities-complete/20-consumer-detail-identity.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=252ac2cbff0d2d07cb4ba446d1752c9a) ![Consumer — identity verification tab](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/entities/policies-and-entities-complete/21-consumer-tab-identity-verification.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=45ab2667e80443ec7dd069a3b5ae5c30) ![Businesses list](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/entities/policies-and-entities-complete/24-businesses-list.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=cd27c7a8f0cffc5c2d237eec447a5806) ![Business profile — overview](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/entities/policies-and-entities-complete/27-business-detail-overview.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=3faa1201f8b55f3fb64e04012aba9b84) ## Related concepts > **[Consent](/concepts/identity/consent)** > > How to record permission to query a consumer or business. > **[Entitlement](/concepts/governance/entitlement)** > > Why you can read a given field about an entity. > **[Furnishing](/api-overview/furnishing/overview)** > > How contributing data creates and enriches entities. > **[Querying](/api-overview/querying/overview)** > > How consolidated entity data is read back. # Business graph Source: https://docs.solo.one/concepts/trust/business-graph What can actually be verified about a business in the network, and how each branch becomes a reusable trust asset The **business graph** is everything a SOLO network can hold about a legal entity: a core identity, plus the verification events participants have furnished about it. Each branch is a candidate [trust asset](/concepts/trust/trust-assets) — an attested verification another institution can reuse instead of repeating registry lookups, ownership tracing, and risk screening. This page shows what lives in the graph **today**. Every node maps to a real model; branches with no backing model in the current catalog (for example business financials/revenue, banking relationships, or tax-return inventories) are deliberately absent rather than implied. ## The graph ```text theme={null} Business ├── Identity (business) │ ├── business_legal_name, business_dba_name │ ├── business_jurisdiction_of_formation │ ├── business_entity_type │ ├── business_date_of_incorporation │ ├── federal_ein / tax identifier │ ├── business_website_url, business_email, business_phone │ └── employee_count │ ├── Business identity verification (business_identity_verification_event) │ ├── registration_status, registration_identifier │ ├── jurisdiction_of_formation │ ├── operational_existence_status │ ├── tax_id_validation_result, address_verification_result │ ├── is_secretary_of_state_match │ └── formation_document_artifact (evidence) │ ├── Ownership & control (business_ownership_control_verification_event) │ ├── beneficial_owners_identified_count │ ├── control_persons_identified_count │ ├── authorized_representatives_identified_count │ ├── beneficial_ownership_determination_method │ ├── beneficial_ownership_threshold_applied │ ├── control_determination_basis │ └── ownership_evidence_artifact (evidence) │ ├── Risk & compliance (business_risk_compliance_event) │ ├── sanctions_screening_result │ ├── adverse_media_assessment_result │ ├── activity_risk_level │ ├── restricted_activity_assessment_result │ └── primary_activity_classification_code / system │ └── Consent (business consent) └── scope, expires_at, consented_fields ``` ## How to read the graph * **Identity** is the [entity](/concepts/identity/entities) itself — the legal name, jurisdiction, and tax identifier that let contributions from different participants accrue to one business. * **Verification branches** are furnished events. A [KYB certificate](/api-overview/querying/kyb-certificate-snapshot) query consolidates the three verification branches — identity, ownership & control, and risk & compliance — into one certificate. * **Registration and addresses** are not separate branches; they are fields inside business identity verification (`registration_status`, `business_registered_address`, `business_operating_address`). * **Consent** is the permission layer that governs reading any branch. ## Each branch carries provenance Every populated KYB sub-product carries the `furnishing_entity_id` and `attestation_event_id` of the participant whose data backed it, plus an `assertions` block recording what was asserted and when. Ownership and identity branches reference concrete evidence artifacts (a cap table, articles of organization). See [Provenance](/concepts/trust/provenance) for the full lineage model. > [!NOTE] > > What you can actually read back is the intersection of the network's > [querying policy](/concepts/governance/querying-policies) and your > [entitlement](/concepts/governance/entitlement). The graph describes what > *can* exist; consent, policy, and entitlement decide what *you* see. ## Reusable trust assets for a business Consolidated through products, the business graph yields reusable assets such as: * **Verified business identity** — registration, jurisdiction, and tax-ID validation from a [KYB certificate](/api-overview/querying/kyb-certificate-snapshot). * **Verified ownership** — beneficial owners and control persons, evidenced. * **Risk & compliance screening** — sanctions, adverse media, and watchlist outcomes. ## Related > **[Consumer graph](/concepts/trust/consumer-graph)** > > The equivalent for consumers. > **[KYB Certificate](/api-overview/querying/kyb-certificate-snapshot)** > > The consolidated business trust asset, field by field. > **[Trust assets](/concepts/trust/trust-assets)** > > What makes a verification reusable. > **[Provenance](/concepts/trust/provenance)** > > The lineage each branch carries. # Consumer graph Source: https://docs.solo.one/concepts/trust/consumer-graph What can actually be verified about a consumer in the network, and how each branch becomes a reusable trust asset The **consumer graph** is everything a SOLO network can hold about a person: a core identity, plus the verification events participants have furnished about them. Each branch is a candidate [trust asset](/concepts/trust/trust-assets) — an attested verification another institution can reuse instead of repeating. This page shows what lives in the graph **today**. Every node maps to a real model; branches with no backing model (for example consumer assets, liabilities, or bank-account inventories) are deliberately absent rather than implied. ## The graph ```text theme={null} Consumer ├── Identity (consumer) │ ├── first_name, last_name │ ├── date_of_birth │ ├── personal_email │ ├── phone_number │ └── social_security_number │ ├── Identity verification — KYC (KYC certificate sub-products) │ ├── Document capture & review (document_capture_event, identity_document) │ ├── Biometric capture & review (biometric_capture_event) │ ├── Liveness capture & review (liveness_check_event) │ ├── Address capture & verification (address_capture_event) │ └── Identity corroboration (identity_corroboration_event) │ └── is_ssn_match, is_name_match, is_dob_match, kyc_decision │ ├── Address verification (address_capture_event) │ └── address_verification_outcome, address_match_quality, address_verification_method_type │ ├── Income (income_verification_event) │ ├── base_salary, monthly_income │ ├── bonus_commission_income, overtime_income │ ├── debt_to_income_ratio │ └── paystub_upload (evidence) │ ├── Employment (employment_verification_event) │ ├── employer_name, job_title │ ├── employment_status, employment_type │ ├── employment_start_date, length_of_employment_months │ └── employer_letter_of_employment_upload (evidence) │ ├── Phone (phone_number_verification) │ └── phone_carrier_name, phone_age_days, phone_risk_score │ ├── Fraud signals (fraud_verification_event) │ ├── confirmed_fraud_indicator │ ├── fraud_attribute_label, fraud_loss_event_category │ └── fraud_event_date │ └── Consent (consumer consent) └── scope, expires_at, consented_fields ``` ## How to read the graph * **Identity** is the [entity](/concepts/identity/entities) itself — the matching keys (SSN, date of birth) that let contributions from different participants accrue to one person. * **Verification branches** are furnished events. The richest is **KYC**, which a [KYC certificate](/api-overview/querying/kyc-certificate-snapshot) query consolidates into up to nine sub-products. Income, employment, phone, and fraud are separate verification-event models that can also be furnished about a consumer. * **Consent** is not data *about* the consumer so much as the permission layer that governs reading any of it. ## Each branch carries provenance A branch only becomes a reusable trust asset because its lineage travels with it. Certificate sub-products always carry the `furnishing_entity_id` and `attestation_event_id` of the participant whose data backed them, plus an `assertions` block recording what was asserted and when. Evidence is concrete — a paystub upload behind an income event, a captured document behind identity verification. See [Provenance](/concepts/trust/provenance) for the full lineage model. > [!NOTE] > > What you can actually read back is the intersection of the network's > [querying policy](/concepts/governance/querying-policies) and your > [entitlement](/concepts/governance/entitlement). The graph describes what > *can* exist; consent, policy, and entitlement decide what *you* see. ## Reusable trust assets for a consumer Consolidated through products, the consumer graph yields reusable assets such as: * **Verified identity** — a [KYC certificate](/api-overview/querying/kyc-certificate-snapshot) consolidating document, biometric, liveness, address, and corroboration evidence. * **Verified address** — address capture and verification outcomes. * **Fraud screening** — [screening list](/api-overview/querying/screening-lists) results drawn from furnished fraud signals. ## Related > **[Business graph](/concepts/trust/business-graph)** > > The equivalent for businesses. > **[KYC Certificate](/api-overview/querying/kyc-certificate-snapshot)** > > The consolidated identity trust asset, field by field. > **[Trust assets](/concepts/trust/trust-assets)** > > What makes a verification reusable. > **[Provenance](/concepts/trust/provenance)** > > The lineage each branch carries. # Provenance Source: https://docs.solo.one/concepts/trust/provenance Who verified a fact, how, when, and on what evidence — the lineage that makes reuse safe Provenance is what separates a SOLO network from a black box. Banks do not want to trust SOLO; they want to trust the **institution that did the verification**, and to see the evidence. So every reusable [trust asset](/concepts/trust/trust-assets) carries its lineage: who contributed it, who attested to it, when, by what method, and on what evidence. > SOLO does not ask participants to trust unattributed data. It preserves the > lineage of every reusable verification. ## The lineage every certificate carries When a [product query](/api-overview/querying/products) consolidates verifications into a certificate, each populated sub-product is self-describing: ```json theme={null} "business_identity_verification": { "furnishing_entity_id": "e1d2c3b4-…", "attestation_event_id": "f0a1b2c3-…", "assertions": { "business_identity_verification_assertion": true, "business_identity_verification_performed_timestamp": "2026-05-02T00:00:00Z" }, "data": { "business_formation_document_artifact": "articles_of_organization.pdf", "business_identity_verification_sources": ["state_registry"], "business_tax_id_validation_result": "match" } } ``` | Lineage element | Field | What it tells the reader | | - | - | - | | **Contributor / verifier** | `furnishing_entity_id` | Which participant furnished and stands behind this verification | | **Attestation** | `attestation_event_id` | The attestation record linking that participant to the asserted facts | | **Assertion + time** | `assertions.*` and its `…_timestamp` | What was asserted, and when the verification was performed | | **Method & sources** | `data.*_sources`, `*_method`, `*_result` | How it was verified (e.g. `primary_government_registry`, `automated_and_manual`, `full_match`) | | **Evidence** | artifact/upload fields (e.g. `…_artifact`, `paystub_upload`) | The concrete document behind the assertion | | **As-of date** | the certificate's `certificate_as_of_date` | How current the consolidated result is | The [coverage check](/api-overview/querying/coverage-check) goes a step further and returns a human-readable `furnishing_entity_name` alongside the `furnishing_entity_id`, so you can see *which banks* can satisfy a product for a subject before you query. ## One subject, many furnishers An entity profile is a **shared subject**, not a record any one participant owns. In a healthy network several participants furnish about the same consumer or business — one contributes identity verification, another fraud signals, a third business registration. All of it accrues to the same profile, and each contribution keeps its own attribution. Two consequences follow: 1. **You don't own the whole profile.** Your furnished fields sit alongside others'. Updating your contribution never overwrites theirs. 2. **What you can read is governed, not automatic.** Sharing a subject with another furnisher does not mean you see their data — a query returns the intersection of the [querying policy](/concepts/governance/querying-policies) and your [entitlement](/concepts/governance/entitlement). ## How the network picks which furnisher's verification to reuse When multiple furnishers have verified the same thing, the query does not guess. The querying policy defines **provenance options** — ordered, filtered preferences for which furnished events qualify and in what priority. At resolution time the network applies the policy's filters and selects the **oldest matching event** for each sub-product across the allowed networks, falling back through the policy's priority order until a qualifying verification is found. ```mermaid theme={null} flowchart TB Q[Query] --> Gather["Gather furnished events across network_ids"] Gather --> Filter["Apply policy provenance options + freshness filters"] Filter --> Pick["Select qualifying event per sub-product"] Pick --> Decision{All required sub-products satisfied?} Decision -- yes --> Cert["Issue certificate · 200, with per-source attribution"] Decision -- no --> Empty["204 No Content"] ``` You can also narrow reuse yourself: pass `furnishing_entity_ids` on a query to consider only specific contributors. ## Freshness is part of provenance A verification is only reusable while it is current enough for the reader's policy. Querying policies enforce **freshness windows** (for example, "identity corroboration within the last 30 days") using certificate-level fields such as `days_since_certificate_as_of_date`. Data outside the window is not reused — the query returns `204` rather than handing back a stale answer dressed up as fresh. This is why freshness, like attribution, is a first-class part of what makes reuse safe. ## Why provenance is exposed on purpose For most software, exposing internals is a mistake. For SOLO it is the product: participants reuse each other's work *because* they can see who did it, how, and when. Provenance turns "trust SOLO" into "trust the network, transparently." > [!NOTE] > > Provenance here means the lineage that exists today — `furnishing_entity_id`, > `attestation_event_id`, assertion timestamps, source/method/evidence fields, and > policy freshness. It does not include reuse counters, dispute counts, or > contributor reputation scores, which are not part of the current platform. ## Related > **[Trust assets](/concepts/trust/trust-assets)** > > The reusable unit provenance describes. > **[Entitlement](/concepts/governance/entitlement)** > > Why you can read a given field about a subject. > **[Querying policies](/concepts/governance/querying-policies)** > > Freshness windows and provenance options. > **[Coverage check](/api-overview/querying/coverage-check)** > > See which furnishers can satisfy a product before you query. # Trust assets Source: https://docs.solo.one/concepts/trust/trust-assets The reusable unit of the network — an attested, evidenced verification that another institution can safely build on A **trust asset** is the reusable unit of a SOLO network: a verification that one institution completed, backed by evidence, attributed to its source, and governed by permission — so that another institution can reuse it instead of repeating the work. "Trust asset" is a lens, not a new API object. It names what already exists in the platform when you put the pieces together: a furnished [verification](/api-overview/furnishing/overview) event, its supporting evidence, the [attestation](/concepts/trust/provenance) that records who stands behind it, and the [consent](/concepts/identity/consent) and [entitlement](/concepts/governance/entitlement) that decide who may reuse it. A [product query](/api-overview/querying/products) assembles trust assets into a certificate. ## What makes a verification a *trust asset* A raw data point — `revenue: $5.2M` — is not reusable on its own. Another bank cannot act on a number with no idea who produced it, how, or when. A trust asset carries the context that makes reuse safe: | Dimension | The trust question | Where it lives in the API | | - | - | - | | **Subject** | Who is this about? | The [entity](/concepts/identity/entities) (consumer or business) | | **Assertion** | What was attested, and when? | The verification event's `assertions` block (e.g. `business_identity_verification_assertion`, with its `…_timestamp`) | | **Evidence** | Why should anyone believe it? | The verification event's `data` block and artifact fields (e.g. `business_formation_document_artifact`) | | **Attribution** | Who verified it? | `furnishing_entity_id` + `attestation_event_id` on every populated sub-product | | **Permission** | Who is allowed to reuse it? | The subject's [consent](/concepts/identity/consent) | | **Precision** | Which fields may *this* reader see? | [Entitlement](/concepts/governance/entitlement), earned by participation | | **Reuse eligibility** | Is it fresh and complete enough to count? | The [querying policy](/concepts/governance/querying-policies)'s field and freshness filters | | **Consolidation** | How is it delivered for reuse? | A [certificate](/api-overview/querying/products) (`certificate_id`, with its as-of date) | A data aggregator returns the first row of that table. SOLO returns all of it. ## A trust asset, concretely Here is one populated sub-product from a [KYB certificate](/api-overview/querying/kyb-certificate-snapshot) query — a single trust asset in the wild: ```json theme={null} "business_identity_verification": { "furnishing_entity_id": "e1d2c3b4-…", "attestation_event_id": "f0a1b2c3-…", "assertions": { "business_identity_verification_assertion": true, "business_identity_verification_performed_timestamp": "2026-05-02T00:00:00Z" }, "data": { "business_formation_document_artifact": "articles_of_organization.pdf", "business_jurisdiction_of_formation": "DE", "business_registration_identifier": "7423918", "business_registration_status": "active", "business_identity_verification_sources": ["state_registry"], "business_tax_id_validation_result": "match" } } ``` Read it as a trust asset: * **Subject** — the business the certificate was issued for (`business_identity_id` on the enclosing response). * **Assertion** — business identity *was* verified, on `2026-05-02`. * **Evidence** — backed by an articles-of-organization artifact, a state registry source, and a tax-ID match. * **Attribution** — furnished and attested by the participant identified by `furnishing_entity_id` / `attestation_event_id`. * **Reuse** — surfaced to you only because consent permitted the read, your entitlement covered these fields, and the data passed the policy's freshness window. The next institution onboarding this business does not re-pull the registry or re-collect the formation document. It queries the certificate and reuses this asset — while the contributor's attribution travels with it. ## How trust assets are created and reused ```mermaid theme={null} flowchart LR Inst["Institution verifies a subject"] --> Furn["Furnish verification + evidence"] Furn --> Asset[("Trust asset: assertion + evidence + attestation")] Asset --> Policy["Querying policy: fresh + complete enough?"] Consent["Subject consent"] --> Policy Ent["Reader entitlement"] --> Policy Policy --> Cert["Certificate issued · reused by another institution"] ``` 1. **Create.** An institution [furnishes](/api-overview/furnishing/overview) the verification it performed. The furnished events, their evidence, and the institution's attestation become a trust asset attributed to that contributor. 2. **Govern.** [Consent](/concepts/identity/consent) decides whether the asset can be read for a subject; [entitlement](/concepts/governance/entitlement) decides which fields a given reader sees; the [querying policy](/concepts/governance/querying-policies) decides whether the asset is fresh and complete enough to count. 3. **Reuse.** A [product query](/api-overview/querying/products) consolidates the eligible trust assets into a [certificate](/api-overview/querying/kyc-certificate-snapshot). If nothing eligible exists, the query returns `204` rather than a stale or unattributed answer. ## The three layers Trust assets sit in the middle of how the platform is organized: | Layer | What it is | Pages | | - | - | - | | **Trust graph** | The subjects and the verifications attached to them | [Consumer graph](/concepts/trust/consumer-graph), [Business graph](/concepts/trust/business-graph) | | **Trust assets** | Attested, evidenced verifications that become reusable | This page, [Provenance](/concepts/trust/provenance) | | **Trust products** | The outcomes a query delivers | [Products](/api-overview/querying/products), [KYC](/api-overview/querying/kyc-certificate-snapshot), [KYB](/api-overview/querying/kyb-certificate-snapshot) | > [!NOTE] > > Trust assets describe data that **exists today** — furnished verification > events, their attestations, and the certificates that consolidate them. SOLO > does not expose a standalone `TrustAsset` object, reuse counters, or > verification "tiers"; the reusable asset is the furnished, attested > verification itself, surfaced through certificate queries. ## Related > **[Provenance](/concepts/trust/provenance)** > > The lineage every trust asset carries. > **[Business graph](/concepts/trust/business-graph)** > > The trust assets that can exist for a business. > **[Consumer graph](/concepts/trust/consumer-graph)** > > The trust assets that can exist for a consumer. > **[Products](/api-overview/querying/products)** > > How trust assets are consolidated and delivered. # Authentication Source: https://docs.solo.one/home/authentication Authenticate with the SOLO Network API using WorkOS-issued bearer JWTs verified over JWKS Every public endpoint under `/v1` requires a bearer token in the `Authorization` header. The token is a JWT issued by **WorkOS**, SOLO's identity provider, when you sign in to the SOLO dashboard. The API verifies it cryptographically on every request — there are no API keys and no shared secrets to manage on your side. ```bash theme={null} Authorization: Bearer ``` Only `GET /health` and `GET /version` are exempt; everything else returns `401 Unauthorized` without a valid token. ## Getting a token Access to the API starts with your organization being provisioned by SOLO. Once that's done, tokens are minted through authentication with the SOLO dashboard. **1. Get your organization provisioned** SOLO onboards your organization into WorkOS and associates it with your entity on the network. If you don't have access yet, contact your SOLO account manager. **2. Sign in** Authenticate through the SOLO dashboard sign-in flow. On success, WorkOS issues an access token — a JWT signed with RS256 — bound to your user and organization. **3. Send the token with every request** Pass the access token in the `Authorization` header using the `Bearer` scheme. The same token works for both the dashboard and direct API calls against that environment. > [!WARNING] > > Treat tokens as secrets. Never commit them to source control, embed them in > client-side code you don't control, or log them. Anyone holding the token can > act as your organization until it expires. ## What the server verifies When a request arrives, the API verifies the token before any handler runs: 1. **Signature.** The token's key ID (`kid`) is looked up in WorkOS's published JWKS endpoint (`https://api.workos.com/sso/jwks/`), and the RS256 signature is verified against that public key. Tokens signed by anything other than WorkOS — or minted for a different environment's client — fail here, because their signing key isn't in the key set. 2. **Standard claims.** The decoded payload is validated, including expiry (`exp`). An expired token is rejected. 3. **Identity resolution.** The verified claims (your user ID, organization, and role) are used to resolve your account and entity on the network. If the token is valid but no matching account exists, the request is rejected with a 401. Endpoints that demand specific permissions additionally check the token's `permissions` (or `role`) claim; a valid token without the required permission receives a `403 Forbidden`. > [!NOTE] > > Verification happens against WorkOS's public keys — the API never sees or > stores your password, and tokens cannot be forged without WorkOS's private > key. ## Token lifetime and refresh WorkOS access tokens are short-lived. Don't cache one and reuse it indefinitely: * **Refresh proactively.** Re-authenticate (or use your sign-in session's refresh mechanism) before the token's `exp` claim passes, rather than waiting for failures. * **Handle 401 as a refresh signal.** If a previously working request starts returning `401` with `"Invalid or expired token"`, obtain a fresh token and retry the request once. Do not retry in a loop with the same token. * **Don't share tokens across environments.** A token is bound to one environment's WorkOS client. Sandbox tokens fail signature lookup in production and vice versa. ## Environments Use the base URL for the environment your credentials were issued in: | Environment | Base URL | | - | - | | Development | `https://api.dev.solo.one` | | Sandbox | `https://api.sandbox.solo.one` | | Production | `https://api.prod.solo.one` | ## Example request A complete authenticated call — searching for a consumer by name within a network using `GET /v1/entities/consumer_identity/search`: ```bash theme={null} curl -G "https://api.sandbox.solo.one/v1/entities/consumer_identity/search" \ -H "Authorization: Bearer $SOLO_TOKEN" \ --data-urlencode "network_id=2f6f6a3e-9d6b-4a4e-8a6f-3a1d1f2b9c10" \ --data-urlencode "first_name=Ada" \ --data-urlencode "last_name=Lovelace" \ --data-urlencode "limit=10" ``` A successful response returns matching consumer identities: ```json theme={null} [ { "id": "7c0b6e2a-1f4d-4f7e-9b2a-5e8c3d1a6f90", "identifier": "consumer-7c0b6e2a", "first_name": "Ada", "last_name": "Lovelace", "personal_email": "ada@example.com", "date_of_birth": "1990-12-10", "created_at": "2026-05-01T12:00:00Z" } ] ``` The same header works on every endpoint — swap the path and method: ```bash theme={null} curl -X POST "https://api.sandbox.solo.one/v1/consent/consumer_identity" \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ ... }' ``` ## 401 vs 403 The two failure modes mean different things, and you should handle them differently. **`401 Unauthorized` — the API doesn't know who you are.** The token is missing, malformed, expired, or doesn't resolve to an account. Fix the token and retry. ```json theme={null} { "detail": "Requires authentication" } ``` ```json theme={null} { "detail": "Invalid or expired token" } ``` ```json theme={null} { "detail": "Could not validate credentials or find account" } ``` **`403 Forbidden` — the API knows who you are, and the answer is no.** The token verified successfully, but it lacks a required permission, or your account isn't allowed to perform this operation on this resource. A retry with the same credentials will fail the same way; this is a provisioning or authorization problem, not a token problem. ```json theme={null} { "detail": "Not enough permissions" } ``` Domain-level authorization failures (for example, operating on a network you don't belong to) also return `403`, with the full error envelope described in [Errors](/home/errors): ```json theme={null} { "detail": "Permission denied", "error_code": "PERMISSION_DENIED", "request_id": "a1b2c3d4-..." } ``` > [!TIP] > > Rule of thumb: on `401`, refresh the token and retry once. On `403`, stop and > check your roles and network membership — retrying won't help. ## Troubleshooting | Symptom | Status and body | Cause | Fix | | - | - | - | - | | No `Authorization` header sent | `401` — `"Requires authentication"` | Header missing entirely | Add `Authorization: Bearer $SOLO_TOKEN` to the request | | Wrong scheme (e.g. `Authorization: Token …` or a bare token) | `401` — `"Requires authentication"` | The header isn't a valid `Bearer` credential, so it's treated as absent | Use exactly `Bearer ` — capital B, single space | | Expired token | `401` — `"Invalid or expired token"` | The `exp` claim has passed | Obtain a fresh token and retry once | | Token from the wrong environment | `401` — `"Invalid or expired token"` | The signing key isn't in this environment's JWKS | Use a token issued for the environment you're calling | | Garbled or truncated token | `401` — `"Invalid or expired token"` | The JWT can't be decoded or its key can't be resolved | Re-copy the token; check for whitespace or truncation in your env var | | Valid token, unknown account | `401` — `"Could not validate credentials or find account"` | Your user verified but no account/entity exists for it yet | Usually first-time setup — contact SOLO support | | Valid token, missing permission | `403` — `"Not enough permissions"` | The `permissions`/`role` claim lacks what the endpoint requires | Ask your administrator to update your role | | Persistent `500` — `"Authentication service is unavailable"` | `500` | Server-side identity configuration problem (not your token) | Report to SOLO support with your `request_id` | If you're stuck, capture the full response body — including the `request_id` when present — and quote it to support. See [Errors](/home/errors#request-ids) for how request IDs are correlated. ## Best practices > **Keep tokens server-side** > > Make API calls from your backend and keep tokens out of code you don't > control. A bearer token carries your organization's full standing on the > network for as long as it's valid. > **Store the token in an environment variable** > > The examples on this site use `$SOLO_TOKEN` for a reason: keeping the token > in an environment variable (or a secrets manager) keeps it out of shell > history, scripts, and version control. > **Build refresh in from day one** > > Because tokens are short-lived, an integration that fetches one token at > startup will start failing mid-run. Centralize token acquisition behind a > helper that refreshes on expiry, and treat a `401` anywhere as a signal to > refresh once and retry. > **Use least-privilege roles** > > Request only the roles and permissions your integration needs. A > furnish-only service doesn't need querying permissions — limiting the > token's reach limits the blast radius if it leaks. # Errors Source: https://docs.solo.one/home/errors The error response envelope, every HTTP status the API returns, and how to handle each one The SOLO Network API uses standard HTTP status codes and returns structured JSON error bodies. This page documents the exact response shapes, real examples of each status, and what your integration should do in response. ## The error envelope Application errors — anything the API's domain logic rejects — return a consistent envelope: ```json theme={null} { "detail": "Consent record not found", "error_code": "RESOURCE_NOT_FOUND", "request_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" } ``` | Field | Type | Presence | Meaning | | - | - | - | - | | `detail` | string | always | Human-readable description of what went wrong. Safe to log and show to operators; not guaranteed stable, so don't branch on its text. | | `error_code` | string | always | Machine-readable category (see table below). Stable — branch on this. | | `request_id` | string | when available | Unique ID for this request, assigned by the API. Quote it to support. | | `errors` | array | occasionally | Itemized sub-errors for operations that validate many things at once (for example, rows in a file upload). | Two kinds of responses use a **shorter shape** — just `{"detail": "..."}` with no `error_code`: * **`422` request-shape validation**, where FastAPI rejects the request before it reaches domain logic. * **Authentication-layer `401`/`403`**, raised while verifying the bearer token (see [Authentication](/home/authentication#401-vs-403)). ```json theme={null} { "detail": "body -> first_name: Field required" } ``` ### Error codes | `error_code` | Status | Raised when | | - | - | - | | `VALIDATION_ERROR` | `400` | The request was well-formed but violates a domain rule | | `AUTHENTICATION_REQUIRED` | `401` | Domain logic could not establish who you are | | `PERMISSION_DENIED` | `403` | You're authenticated but not allowed to do this | | `RESOURCE_NOT_FOUND` | `404` | The referenced resource doesn't exist (or isn't visible to you) | | `RESOURCE_CONFLICT` | `409` | The operation conflicts with existing state (duplicate, version mismatch) | | `OPERATION_FAILED` | `500` | A known internal operation failed | | `INTERNAL_ERROR` | `500` | An unexpected, unhandled error | ## HTTP status codes | Code | Meaning on this API | | - | - | | `200 OK` | Success. The body contains the requested data. | | `204 No Content` | A product query resolved successfully but produced **no result** — the available data did not satisfy the policy's requirements. The body is empty; the `X-Ref-Id` response header carries the query's reference ID. | | `400 Bad Request` | Domain validation failed: the request was structurally valid but violates a business rule (e.g. consent not gathered, no updatable fields provided). | | `401 Unauthorized` | Missing, malformed, expired, or unresolvable bearer token. | | `403 Forbidden` | Valid token, but the operation isn't permitted for your account or role. | | `404 Not Found` | The resource doesn't exist within your network scope. | | `409 Conflict` | The resource already exists or the operation conflicts with current state. | | `422 Unprocessable Entity` | Request-shape validation failed: a missing, mistyped, or malformed field. The `detail` string names the field path. | | `500 Internal Server Error` | Something went wrong on SOLO's side. Logged and monitored automatically. | > [!NOTE] > > A `204` is not an error. It means the query ran — and is accounted for — > but the network could not assemble a result that meets the policy's > requirements for this entity. Capture the `X-Ref-Id` header before treating > the response as "no data": it's the reference for that specific query if you > need to follow up. ## Examples by status Each example below is a real response produced by the API's handlers. > **400 — domain validation failed** > > Creating a consumer consent without affirming consent was gathered: > > ```json theme={null} > { > "detail": "Consent cannot be created without prior consumer consent gathering", > "error_code": "VALIDATION_ERROR", > "request_id": "a1b2c3d4-..." > } > ``` > > Updating a consent record with an empty change set: > > ```json theme={null} > { > "detail": "At least one of scope, expires_at, or consented_fields must be provided", > "error_code": "VALIDATION_ERROR", > "request_id": "a1b2c3d4-..." > } > ``` > > Querying a product without identifying the subject: > > ```json theme={null} > { > "detail": "Either consent_id or consumer_identity_id must be provided", > "error_code": "VALIDATION_ERROR", > "request_id": "a1b2c3d4-..." > } > ``` > **401 — authentication failed** > > Authentication-layer rejections use the short shape: > > ```json theme={null} > { "detail": "Requires authentication" } > ``` > > ```json theme={null} > { "detail": "Invalid or expired token" } > ``` > > A valid token whose user has no account on the network yet: > > ```json theme={null} > { "detail": "Could not validate credentials or find account" } > ``` > **403 — permission denied** > > A token lacking a required permission (short shape, from the auth layer): > > ```json theme={null} > { "detail": "Not enough permissions" } > ``` > > A domain-level authorization failure (full envelope): > > ```json theme={null} > { > "detail": "Permission denied", > "error_code": "PERMISSION_DENIED", > "request_id": "a1b2c3d4-..." > } > ``` > **404 — resource not found** > > Reading a consent that doesn't exist in the given network scope: > > ```json theme={null} > { > "detail": "Consent record not found", > "error_code": "RESOURCE_NOT_FOUND", > "request_id": "a1b2c3d4-..." > } > ``` > > Configuring a furnishing policy by an unknown ID: > > ```json theme={null} > { > "detail": "FurnishingPolicy with id 4f6e2a90-... not found", > "error_code": "RESOURCE_NOT_FOUND", > "request_id": "a1b2c3d4-..." > } > ``` > > > [!NOTE] > > > > `404` also covers resources that exist but are outside your network > > scope — the API does not distinguish "doesn't exist" from "not visible > > to you". > **409 — conflict** > > Creating something that already exists, or modifying state that has moved > underneath you: > > ```json theme={null} > { > "detail": "Resource conflict", > "error_code": "RESOURCE_CONFLICT", > "request_id": "a1b2c3d4-..." > } > ``` > > Re-read the current state before deciding whether to retry — a `409` on a > create often means the resource is already there and your work is done. > **422 — request-shape validation** > > The `detail` is a single string in the form `: `, > where the path walks from the request part (`body`, `query`, `path`) down > to the offending field. Only the **first** validation error is reported. > > ```json theme={null} > { "detail": "body -> first_name: Field required" } > ``` > > ```json theme={null} > { "detail": "body -> date_of_birth: Input should be a valid date" } > ``` > > ```json theme={null} > { "detail": "query -> network_id: Field required" } > ``` > > Fix the named field and resend. The > [API Reference](/api-reference/introduction) documents the expected type > and format of every field. > **500 — server error** > > Unexpected failures never leak internals: > > ```json theme={null} > { > "detail": "An unexpected error occurred", > "error_code": "INTERNAL_ERROR", > "request_id": "a1b2c3d4-..." > } > ``` > > Known-but-failed internal operations return `OPERATION_FAILED` instead. > Both are logged and monitored on SOLO's side; include the `request_id` > when reporting one. ## Validation errors vs domain errors The API draws a sharp line between two kinds of "bad request", and the status code tells you which side you're on: * **`422` — the request itself is malformed.** A required field is missing, a date doesn't parse, a UUID is garbled. The request never reached business logic. This is a bug in the calling code: fix the payload. The body is the short shape with a `field path: message` detail. * **`400` — the request is well-formed but the *operation* is invalid.** Every field parsed, but a business rule said no: consent wasn't gathered, an update contained nothing updatable, a required identifier was absent given the combination of inputs. The body is the full envelope with `"error_code": "VALIDATION_ERROR"`. Fixing this usually means changing what you're asking for, not how you're serializing it. Treat `422` as a build-time problem (your integration is constructing requests wrong) and `400` as a run-time problem (this particular operation isn't allowed right now, or needs different inputs). ## Request IDs Every request is assigned a `request_id` as it enters the API, and error envelopes include it whenever it's available. The same ID is attached to SOLO's internal logs and traces for that request. When contacting support about a failed call, always include: 1. The `request_id` from the error body (or the `X-Ref-Id` header for `204` product-query responses). 2. The full response body and status code. 3. The endpoint, method, and approximate timestamp (with timezone). With a `request_id`, support can jump directly to the exact request; without one, they're searching by time window. ## Retry guidance | Status | Retry? | How | | - | - | - | | `500` | Yes | Retry with exponential backoff and jitter. If it persists, stop and report the `request_id`. | | `401` | Once | Refresh your token first, then retry a single time. Never loop with the same token. | | `409` | Maybe | Re-read current state first; retry only if the conflict has been resolved. | | `400`, `403`, `404`, `422` | No | The same request will fail the same way. Fix the request, your permissions, or the referenced resource. | | `204` | No | Not an error — the query succeeded with no result. Re-query only when you expect the underlying data to have changed. | > [!WARNING] > > Product query endpoints are billable. Build retry logic carefully so a > transient failure doesn't turn into a loop of repeated billable queries — > cap attempts, and use the non-billable `POST /v1/products/check` coverage > check when you only need to know whether data exists. # High-level concepts Source: https://docs.solo.one/home/overview The six building blocks of the SOLO Network, the data flow that connects them, and how together they enforce privacy The SOLO Network turns one institution's completed verification work into another institution's trusted starting point. Participants furnish verified data about consumers and businesses, and query that data back — under clear rules about who can read what, and why. Six concepts power everything you do with the API. Once you understand them and the order they come into play, every endpoint in the [reference](/api-reference/introduction) reads naturally. The architecture is not internal plumbing you can ignore — it *is* the trust mechanism. Each building block exists to answer a question a bank's risk and compliance teams must be able to answer before they reuse another institution's work: | Building block | Trust question it answers | | - | - | | Network | Inside which boundary of trusted participants does this data live? | | Entity | Who is the subject being verified? | | Consent | Did the subject permit this read? | | Product | What standardized, reusable result am I asking for? | | Policy | What may be read, and how fresh must it be, to count? | | Entitlement | Of what the policy exposes, what may *I* see — and why? | Read [How SOLO is different](/home/why-solo) for why this matters, and [Trust assets](/concepts/trust/trust-assets) for how a furnished verification becomes something reusable. ## The building blocks ### Network A [network](/concepts/governance/networks) is the trust boundary everything else lives inside. It groups a set of participating institutions, defines which products are available to them, and contains the data they furnish. Nothing crosses a network boundary: a record furnished into one network is invisible to every other, and every API call that touches data — a consent lookup, an entity search, a product query — is scoped by a `network_id`. Within a network, each participant holds one or more [roles](/concepts/governance/network-roles) — governor, furnisher, querier — that determine which operations it may perform, and [governance rules](/concepts/governance/network-governance) determine how the network itself is administered and changed. ### Entity An [entity](/concepts/identity/entities) is a subject of the network: a **consumer** (a person) or a **business**. Every furnish writes data about an entity, and every query reads data about one. Entities have a core identity — name, email, SSN or EIN, date of birth — that the network uses to recognize when two participants are talking about the same subject, so that data furnished by one bank can be consolidated with data furnished by another. You can look up existing entities with `GET /v1/entities/consumer_identity/search` and `GET /v1/entities/business_identity/search`, both scoped by a required `network_id`. ### Consent [Consent](/concepts/identity/consent) is the subject's permission, captured as a first-class record. Before you query data about a consumer or business, you record that they agreed — `POST /v1/consent/consumer_identity` or `POST /v1/consent/business_identity` — and receive a `consent_id` that you pass with every subsequent query. A consent record carries a scope, an optional expiry, and the set of consented fields; scope, expiry, and consented fields can be updated later, but the identity fields are immutable, because the record is an audit artifact of who consented and when. The API refuses to create a consumer consent unless you affirm that consent was actually gathered from the consumer first. ### Product A [product](/api-overview/querying/products) is the unit of data you furnish or query — a standardized package rather than a raw table. The network currently offers KYC and KYB certificates (consolidated identity-verification outcomes for consumers and businesses respectively) and screening lists (a bank-specific bad-actor list and a cross-bank financial-crimes watch list). Products expose `query` and, where applicable, `furnish` operations under `/v1/products/{product}/…`. A non-billable soft check, `POST /v1/products/check`, reports per-furnisher field [coverage](/api-overview/querying/coverage-check) for a product before you commit to a billable query. ### Policy A policy is a per-network, per-product rulebook authored by the network governor. A [querying policy](/concepts/governance/querying-policies) defines what a product is allowed to read — which models and which fields appear in query results for that network. A [furnishing policy](/concepts/governance/furnishing-policies) defines what a product accepts on write and how submitted data is validated and transformed. Policies are created as shells (`POST /v1/networks/policies/querying`, `POST /v1/networks/policies/furnishing`) and then configured field by field via their `PUT /v1/networks/policies/…/{policy_id}/configuration` endpoints. A query names the policy it runs under, so the same product can behave differently in different networks. ### Entitlement [Entitlement](/concepts/governance/entitlement) answers the last question: of the fields the policy exposes, which ones can *you* see? Entitlement is earned through participation — by previously furnishing data about an entity or querying it — and is evaluated per field at query time. Two participants issuing the identical query against the same entity can receive different results, each limited to the data their own history entitles them to. This is the mechanism that makes a shared network safe: contributing participants are never exposing data to passive observers. ## The data flow The building blocks come into play in a fixed order. Here is the lifecycle of a participant from joining a network to reading its first query result: ```mermaid theme={null} sequenceDiagram participant P as Participant participant API as SOLO API (/v1) participant N as Network Note over P,N: 1. Join — governor grants roles P->>API: GET /v1/entities/consumer_identity/search?network_id=… API->>N: scope to network, check role N-->>P: matching entities Note over P,N: 2. Consent — subject grants permission P->>API: POST /v1/consent/consumer_identity API-->>P: consent_id Note over P,N: 3. Furnish — contribute verified data P->>API: POST /v1/products/kyc_certificate_snapshot/furnish API->>N: validate against furnishing policy, store Note over P,N: 4. Query — read consolidated data P->>API: POST /v1/products/kyc_certificate_snapshot/query (consent_id) API->>N: resolve policy, consent, entitlement N-->>P: consolidated certificate (or 204 if no result) ``` 1. **Join a network.** Your organization is added by the network governor and assigned roles. From this point your token resolves to an account with capabilities inside that network. See [Join a network](/home/quickstart/join-a-network). 2. **Create consent.** Record the subject's permission and hold on to the returned `consent_id`. Without it, product queries fail. 3. **Furnish.** Contribute data through a product's `furnish` endpoint — or in bulk as [JSONL files](/api-overview/furnishing/file-upload) from the dashboard or [SFTP](/api-overview/sftp/overview). For files, the network's furnishing policies map and validate each record. 4. **Query.** Ask a product for consolidated data, citing your `consent_id` and the network policy to run under. The result is assembled from every furnisher's contributions — filtered through the policy and your entitlement. If the available data cannot satisfy the policy's requirements, you receive an empty `204 No Content` rather than a partial answer. ## How the pieces enforce privacy The model is easiest to remember as three independent gates, each answering a different question. All three must open before a field reaches your response. | Gate | Question it answers | Granted by | | - | - | - | | Capability | May you perform this operation at all? | Your [roles](/concepts/governance/network-roles) in the network | | Permission | Did the subject agree to this read? | A valid [consent](/concepts/identity/consent) record | | Visibility | Which fields may you actually see? | Your [entitlement](/concepts/governance/entitlement), within the policy's bounds | **Roles grant capability.** A participant without the querier role cannot query anything, no matter how much consent it holds. Roles are assigned by the network governor and checked on every request. **Consent grants permission.** A querier with full capability still cannot read a specific consumer's data without that consumer's consent record — scoped, optionally time-limited, and auditable after the fact. **Entitlement grants visibility.** Even with capability and permission, you see only the fields your participation has earned, and never more than the network's querying policy exposes to anyone. The policy is the ceiling; entitlement is your position under it. Because the gates are independent, no single actor can bypass the system: the governor controls policies but not consent, the subject controls consent but not entitlement, and your own history determines entitlement but not capability. ## Where to go next If you learn best by doing, the [Quickstart](/home/quickstart) chains the full flow — consent, furnish, query — into a few `curl` commands. If you are about to write integration code, read [Authentication](/home/authentication) to set up your bearer token and [Errors](/home/errors) to handle failures correctly — in particular the distinction between a `204` empty result, a `400` domain rejection, and a `422` malformed request, all of which you will encounter while integrating. For depth on any single building block, follow its link above into the concepts section. Two reading paths are worth calling out: if your organization primarily contributes data, continue from [Furnishing](/api-overview/furnishing/overview) into [file upload](/api-overview/furnishing/file-upload) and the [SFTP guide](/api-overview/sftp/getting-started) for bulk ingestion; if it primarily consumes data, continue from [Querying](/api-overview/querying/overview) into the individual product pages — [KYC certificate](/api-overview/querying/kyc-certificate-snapshot), [KYB certificate](/api-overview/querying/kyb-certificate-snapshot), and [screening lists](/api-overview/querying/screening-lists). ## In the dashboard ![Home — querying and furnishing checklists](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/01-dashboard.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=770ca8a261c52728dee25b2db5ebce0e) > **[Querying](/api-overview/querying/overview)** > > How a query resolves across network, policy, consent, and entitlement. > **[Furnishing](/api-overview/furnishing/overview)** > > How contributed data is validated, stored, and made queryable. # Quickstart Source: https://docs.solo.one/home/quickstart Get up and running on the SOLO Network in a few minutes The SOLO Network lets member banks contribute verified data about consumers and businesses, and read consolidated views of that data back — all governed by network membership, [consent](/concepts/identity/consent), and [entitlement](/concepts/governance/entitlement). This quickstart sequences the three workflows you'll use most, each as a self-contained walkthrough with complete requests and responses you can adapt directly. ## The sequence Work through the guides in order the first time: 1. **[Join a network](/home/quickstart/join-a-network)** — membership is arranged with your SOLO account manager; this guide shows you how to verify that your token, role, and `network_id` actually work before you build anything. 2. **[Furnish an entity](/home/quickstart/furnish-an-entity)** — record consent, then contribute KYC certificate data about a consumer into your network. 3. **[Query a product](/home/quickstart/query-a-product)** — reuse the consent and read a consolidated KYC certificate back, including how to interpret a `200` versus a `204` and the `X-Ref-Id` header. Furnishing and querying are independent capabilities, but they reinforce each other: furnishing data about a consumer earns you [entitlement](/concepts/governance/entitlement) to read that consumer's data on future queries. By the end of the sequence you'll have made every call a production integration makes: a membership sanity check, a consent record, a furnish submission, and a billable product query — and you'll know how to read each response, including the empty ones. ```mermaid theme={null} flowchart LR Join[Join a network] --> Consent[Record consent] Consent --> Furnish[Furnish an entity] Consent --> Query[Query a product] Furnish -.earns entitlement.-> Query ``` ## What you'll need | Requirement | Where it comes from | Used for | | - | - | - | | **Network membership + role** | Arranged with your SOLO account manager | Furnisher role to furnish, querier role to query | | **Bearer token (SDK token)** | Issued by your SOLO account manager | The `Authorization` header on every request | | **`network_id`** | Provided when your organization joins a network | Scoping every consent, search, furnish, and query | Export your token once so every example in these guides works as-is: ```bash theme={null} export SOLO_TOKEN="your-sdk-token-here" ``` All endpoints live under `https://api.solo.one/v1` and authenticate with the token as a Bearer credential: ```bash theme={null} curl https://api.solo.one/v1/entities/consumer_identity/search \ -H "Authorization: Bearer $SOLO_TOKEN" ``` See [Authentication](/home/authentication) for token claims and lifetimes, and [Errors](/home/errors) for the error envelope every endpoint shares. The walkthroughs use the consumer-side endpoints (`/v1/consent/consumer_identity`, `/v1/entities/consumer_identity/search`, `/v1/products/kyc_certificate_snapshot/...`). The business-side equivalents — `POST /v1/consent/business_identity`, `GET /v1/entities/business_identity/search`, and the KYB certificate under `/v1/products/kyb_certificate_snapshot` — follow the same patterns, so everything you learn here transfers directly. > [!NOTE] > > Every error response uses the same shape: a human-readable `detail` plus a > machine-readable `error_code` (for example `AUTHENTICATION_REQUIRED` or > `RESOURCE_NOT_FOUND`). Each walkthrough includes a troubleshooting section > with the exact failures you're likely to hit. ## Choose your path > **[Join a network](/home/quickstart/join-a-network)** > > Verify your token, membership, and role with your first two API calls. > **[Furnish your first entity](/home/quickstart/furnish-an-entity)** > > Record consent, then contribute KYC data about a consumer. > **[Query your first product](/home/quickstart/query-a-product)** > > Read a consolidated KYC certificate back from the network. > [!TIP] > > New to the platform's vocabulary? Read the > [high-level concepts](/home/overview) first — entities, products, policies, > networks, consent, and entitlement in a few minutes. For the full endpoint > catalog, see the [API reference](/api-reference/introduction). # Golden path: business lending onboarding Source: https://docs.solo.one/home/quickstart/business-lending-onboarding Onboard a business by reusing verified trust assets first, then contributing only the work you had to do yourself This golden path shows the **reuse-first** pattern for onboarding a business: identify the subject, check what the network already holds, reuse it, do only the missing work, and contribute your verification back. It chains real endpoints end to end and uses the business / KYB side throughout. ```mermaid theme={null} flowchart LR Find["Identify business"] --> Consent["Record consent"] Consent --> Check["Coverage check"] Check --> Query["Query KYB certificate"] Query -->|"200 reuse"| Decide["Underwrite"] Query -->|"204 / gaps"| Furnish["Furnish missing verification"] Furnish --> Requery["Re-query"] Requery --> Decide Decide --> Publish["Contribution stays in the network"] ``` ## Prerequisites > [!NOTE] > > * Network membership with the **querier** role (and **furnisher** role for > steps 5–6), arranged with your SOLO account manager. > * A bearer token exported as `SOLO_TOKEN` (see > [Authentication](/home/authentication)). > * Your `network_id`, and the KYB `product_id` for the coverage check. **1. Identify the business** Resolve the applicant to a network profile before anything else, so reused and contributed work consolidate on one subject. ```bash theme={null} curl -G https://api.solo.one/v1/entities/business_identity/search \ -H "Authorization: Bearer $SOLO_TOKEN" \ --data-urlencode "network_id=9f1c0c2e-…" \ --data-urlencode "business_legal_name=Acme Coffee" \ --data-urlencode "federal_ein=123456789" ``` Keep the `id` from any confident match to link it on consent. No match is fine — consent will create the profile. **2. Record business consent** Capture permission to query the business and get a `consent_id`. ```bash theme={null} curl -X POST https://api.solo.one/v1/consent/business_identity \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "did_gather_consent_from_business_prior": true, "business_consent_event_identity": { "business_legal_name": "Acme Coffee LLC", "business_jurisdiction_of_formation": "DE", "business_registration_identifier_from_jurisdiction_of_formation": 7423918, "business_tax_identifier_type": "EIN", "business_tax_identifier_value": "12-3456789" } }' ``` The response carries the `consent_id` you'll pass on the coverage check and query. See [Consent](/concepts/identity/consent) for the full shape. **3. Check what the network already holds** Run the non-billable [coverage check](/api-overview/querying/coverage-check) to see whether reusable KYB [trust assets](/concepts/trust/trust-assets) already exist — and which furnishers can satisfy them — before spending a billable query. ```bash theme={null} curl -X POST https://api.solo.one/v1/products/check \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "product_id": "b4a1d6e8-…", "consent_id": "a3f0b9c7-…", "network_ids": ["9f1c0c2e-…"] }' ``` Each entity in the response carries a `furnishing_entity_name` and a `complete` flag. A furnisher with `complete: true` can satisfy the KYB product for this business — reuse is available. **4. Query the KYB certificate (reuse)** Read the consolidated certificate. This reuses identity, ownership & control, and risk/compliance verification already furnished by participants, each block carrying its source's `furnishing_entity_id` and `attestation_event_id`. ```bash theme={null} curl -X POST https://api.solo.one/v1/products/kyb_certificate_snapshot/query \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "consent_id": "a3f0b9c7-…", "network_ids": ["9f1c0c2e-…"], "policy_id": "5e7d2a14-…" }' ``` A `200` returns a certificate you can underwrite against. A `204` means the available data did not satisfy your policy — continue to step 5. **5. Do only the missing work, then furnish it** For whatever the certificate could not supply (a `204`, or a sub-product the coverage check showed as incomplete), perform that verification yourself and contribute it back: ```bash theme={null} curl -X POST https://api.solo.one/v1/products/kyb_certificate_snapshot/furnish \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "network_id": "9f1c0c2e-…", "application_date": "2026-05-28", "records": [ { "business_legal_name": "Acme Coffee LLC", "business_ein": "12-3456789" } ] }' ``` Furnishing earns you [entitlement](/concepts/governance/entitlement) to read this business back later, and your verification becomes a reusable trust asset for the next participant — attributed to you. **6. Re-query and underwrite** Re-run the step 4 query to assemble the now-complete certificate, then use it in your underwriting decision. The `provenance_event_id` / `X-Ref-Id` ties the network's record of the read to your internal decision record. ## Why this order Reuse-first inverts the traditional onboarding loop: instead of collecting everything up front, you collect *only what the network doesn't already have*. The coverage check is free, so it always precedes the billable query; furnishing last means your one verification compounds for every future participant. ## Related > **[Network consumption](/api-overview/querying/overview)** > > The reuse-first read model in depth. > **[KYB Certificate](/api-overview/querying/kyb-certificate-snapshot)** > > The business trust asset, field by field. > **[Coverage check](/api-overview/querying/coverage-check)** > > Pre-flight what already exists. > **[Trust assets](/concepts/trust/trust-assets)** > > What you reuse and what you create. # Furnishing your first entity Source: https://docs.solo.one/home/quickstart/furnish-an-entity Contribute verified data about a consumer into a network Furnishing contributes data about a [consumer or business](/concepts/identity/entities) into a [network](/concepts/governance/networks). In this guide you'll record a consumer's [consent](/concepts/identity/consent), then furnish KYC certificate data for them. Furnishing is also how you earn [entitlement](/concepts/governance/entitlement) — once you've contributed data about a consumer, you can read that consumer's data back on future queries. ## Prerequisites > [!NOTE] > > Before you start, you need: > > * **Network membership with the furnisher role** — arranged with your SOLO > account manager (see > [Joining a network](/home/quickstart/join-a-network)). > * **A bearer token** (SDK token) exported as `SOLO_TOKEN`. See > [Authentication](/home/authentication). > * **Your `network_id`** — the network you furnish into (ask your account > manager if unsure). ## Record consent and furnish **1. Record the consumer's consent** Create a consent record with the consumer's identity, attesting that you gathered their permission before contributing their data: ```bash theme={null} curl -X POST https://api.solo.one/v1/consent/consumer_identity \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "network_id": "9f1c0c2e-3d5b-4a89-b1e2-7f6a8c0d4e21", "did_gather_consent_from_consumer_prior": true, "consumer_consent_event_identity": { "first_name": "Jane", "last_name": "Doe", "date_of_birth": "1990-01-15T00:00:00", "personal_email": "jane.doe@example.com", "phone_number": "5551234567", "social_security_number": "123-45-6789" } }' ``` ```json Response theme={null} { "consent_id": "5f3a8c21-09d4-4b7a-9d52-6e8b1f4c2a30", "field_access_grants": [ { "furnishing_entity_id": null, "field_definitions": [ "fraud_verification_event.confirmed_fraud_indicator", "fraud_verification_event.fraud_attribute_label", "fraud_verification_event.fraud_event_date", "fraud_verification_event.fraud_loss_event_category" ], "effective_from": "2026-06-09", "effective_to": null } ], "created_at": "2026-06-09T22:04:11+00:00", "events": "", "scope": "", "expires_at": null, "consented_fields": null } ``` All six identity fields are required. If you already know the consumer's UUID (for example from an [entity search](/home/quickstart/join-a-network)), pass it as the optional `consumer_identity_id` to link the consent to that existing profile. > [!WARNING] > > `did_gather_consent_from_consumer_prior` is an attestation, not a flag. > Only set it to `true` if you have actually obtained the consumer's > permission — the API rejects the request with a `400` otherwise. You can re-fetch the consent at any time — the response body is identical to the create response: ```bash theme={null} curl -G https://api.solo.one/v1/consent/consumer_identity/5f3a8c21-09d4-4b7a-9d52-6e8b1f4c2a30 \ -H "Authorization: Bearer $SOLO_TOKEN" \ --data-urlencode "network_id=9f1c0c2e-3d5b-4a89-b1e2-7f6a8c0d4e21" ``` **2. Build your records** Each furnish record identifies one consumer by their core identity. All four fields are required: ```json theme={null} { "first_name": "Jane", "last_name": "Doe", "date_of_birth": "1990-01-15", "social_security_number": "123-45-6789" } ``` These identifiers tell the network *which* consumer the data belongs to — the certificate data is associated with that identity. The network matches each record to a consumer (creating one if no match exists) and stores the data under your organization, attributed to you as the furnisher. `records` is a list, so batch as many consumers as you like into one call: ```json theme={null} { "records": [ { "first_name": "Jane", "last_name": "Doe", "date_of_birth": "1990-01-15", "social_security_number": "123-45-6789" }, { "first_name": "John", "last_name": "Smith", "date_of_birth": "1987-09-02", "social_security_number": "987-65-4321" } ] } ``` **3. Furnish into the network** Call the product's furnish endpoint with your network scope and records: ```bash theme={null} curl -X POST https://api.solo.one/v1/products/kyc_certificate_snapshot/furnish \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "network_id": "9f1c0c2e-3d5b-4a89-b1e2-7f6a8c0d4e21", "application_date": "2026-06-09", "records": [ { "first_name": "Jane", "last_name": "Doe", "date_of_birth": "1990-01-15", "social_security_number": "123-45-6789" } ] }' ``` ```json Response theme={null} { "success": true, "submission_id": "1d4f7a92-c3e5-48b0-a6d1-2b9c8e0f5a47" } ``` Two fields scope the furnish: * `network_id` — the network you're furnishing into. The certificate is written straight into it. * `application_date` — when the consumer applied. Required. See [How furnishing works](/api-overview/furnishing/overview) for every channel, and [Furnishing policies](/concepts/governance/furnishing-policies) for how governors map file records into the network. **4. Confirm the consumer is visible** The furnished consumer now appears in network-scoped entity search: ```bash theme={null} curl -G https://api.solo.one/v1/entities/consumer_identity/search \ -H "Authorization: Bearer $SOLO_TOKEN" \ --data-urlencode "network_id=9f1c0c2e-3d5b-4a89-b1e2-7f6a8c0d4e21" \ --data-urlencode "social_security_number=123-45-6789" ``` A `200` with a matching record confirms the furnish landed. You've also earned [entitlement](/concepts/governance/entitlement) to this consumer's data on future queries. ## Inspect the result **Consent response** (step 1): | Field | Meaning | | - | - | | `consent_id` | The token you'll pass on every subsequent query of this consumer. Store it. | | `field_access_grants` | The access grants attached to the consent. Each grant lists its `field_definitions` (as `table.column` strings), the `furnishing_entity_id` it's scoped to (`null` = not furnisher-specific), and its `effective_from` / `effective_to` window. | | `created_at` | When the consent record was created. | | `events` | The consent's event log reference (empty for a brand-new consent). | | `scope` | The consent's scope label. Starts unset; updatable later via `PUT /v1/consent/consumer_identity/{consent_id}`. | | `expires_at` | When the consent lapses. `null` means no expiry has been set. | | `consented_fields` | An optional comma-separated restriction of consented fields. `null` means no field-level restriction. | **Furnish response** (step 3): | Field | Meaning | | - | - | | `success` | `true` — the records were accepted and a furnish event was recorded. | | `submission_id` | The UUID of this submission. Reference it when reconciling with your SOLO account manager. | > [!TIP] > > Identity fields on a consent are immutable — a consent record is an audit > artifact of who consented and when. To consent with different identity data, > create a new consent record. `scope`, `expires_at`, and `consented_fields` > are the only updatable attributes. ## Why consent comes first Furnishing contributes data *about* a person, and the consent record is the audit artifact proving they agreed: who consented, when, and through which identity. Creating it before you furnish keeps your contribution defensible, and the same `consent_id` is what you'll pass when you later [query this consumer back](/home/quickstart/query-a-product) — one consent covers both sides of your participation while it remains valid. The full model, including field access grants and expiry, is covered in [Consent](/concepts/identity/consent). ## Furnishing at scale A JSON furnish accepts multiple `records` per call, which covers most integration patterns. For recurring volume: * **SFTP ingestion** — drop CSV files on a schedule and let the platform ingest them automatically. Start with [SFTP getting started](/api-overview/sftp/getting-started). * **Bulk file workflows** — the JSONL format, record types, and per-record results are covered in [JSONL files](/api-overview/furnishing/file-upload). ## Troubleshooting > **401 — token expired or invalid** > > ```json theme={null} > { > "detail": "Authentication required", > "error_code": "AUTHENTICATION_REQUIRED" > } > ``` > > Your bearer token is missing, malformed, or expired. Re-export `SOLO_TOKEN` > with a fresh token from your SOLO account manager. See > [Authentication](/home/authentication). > **400 — consent not gathered** > > ```json theme={null} > { > "detail": "Consent cannot be created without prior consumer consent gathering", > "error_code": "VALIDATION_ERROR" > } > ``` > > You sent `did_gather_consent_from_consumer_prior: false`. The platform will > not create a consent record without your attestation that the consumer > agreed first. Gather consent, then retry with `true`. > **404 — consent not found in this network** > > ```json theme={null} > { > "detail": "Consent record not found", > "error_code": "RESOURCE_NOT_FOUND" > } > ``` > > Returned by `GET /v1/consent/consumer_identity/{consent_id}` when the `network_id` > query parameter doesn't match the network the consent was created in. > Consents are network-scoped — look them up with the same `network_id` you > created them under. > **403 — missing the furnisher role** > > ```json theme={null} > { > "detail": "Permission denied", > "error_code": "PERMISSION_DENIED" > } > ``` > > Your token is valid and the network exists, but your membership doesn't > carry the furnisher role. Roles are arranged with your SOLO account > manager — see [Network roles](/concepts/governance/network-roles). > **422 — missing or malformed field** > > ```json theme={null} > { > "detail": "body -> records: Field required" > } > ``` > > A required field is missing or the wrong type. The `detail` string names > the first failing field as `location -> field: message` — common culprits > are omitting `application_date`, or leaving an identity > field out of `consumer_consent_event_identity`. ## Next steps > **[Query a product](/home/quickstart/query-a-product)** > > Reuse the `consent_id` and read this consumer's data back. > **[How furnishing works](/api-overview/furnishing/overview)** > > Channels, network routing, and what happens after ingest. > **[SFTP getting started](/api-overview/sftp/getting-started)** > > Automated, recurring bulk ingestion. > **[Why consent is needed](/concepts/identity/consent)** > > The permission layer behind every read. # Joining a network Source: https://docs.solo.one/home/quickstart/join-a-network Understand membership, roles, and your first calls in a network Everything you do on the platform happens inside a [network](/concepts/governance/networks) — a trust boundary that defines who can contribute data, who can read it, and under which policies. This guide gets you from "we signed the paperwork" to "our token works and we can see the network," using two read-only API calls. ## Prerequisites > [!NOTE] > > Before you start, you need: > > * **Network membership and a role** — arranged with your SOLO account manager > (see step 1 below). > * **A bearer token** (SDK token) exported as `SOLO_TOKEN`. See > [Authentication](/home/authentication). > * **Your `network_id`** — the UUID of the network you were added to. ## Set up and verify **1. Get added to a network** Network membership and roles are arranged with your SOLO account manager — there is no self-service signup endpoint. When your organization is added, you receive: * a **`network_id`** — the network you'll furnish into or query from, and * one or more **roles** that define what you can do in it. | Role | What it lets you do | | - | - | | **Furnisher** | Contribute data into the network. | | **Querier** | Run product queries scoped to the network. | | **Governor** | Administer the network's policies and metadata. | Roles grant *capabilities*, not *visibility*. Joining a network — even governing one — does not let you read other members' data. Reading requires [consent](/concepts/identity/consent) from the data subject and [entitlement](/concepts/governance/entitlement) earned by participating. Roles are covered in depth in [Network roles](/concepts/governance/network-roles). **2. Confirm the API is reachable** Before testing your credentials, confirm you can reach the API at all. `/version` requires no special role and tells you which API versions the deployment serves: ```bash theme={null} curl https://api.solo.one/version \ -H "Authorization: Bearer $SOLO_TOKEN" ``` ```json Response theme={null} { "api_versions": ["v1"], "build": { "commit": "9b8a7c6d", "version": "1.42.0" } } ``` All the endpoints in these guides live under the `v1` prefix listed in `api_versions`. The `build` block identifies the exact deployment — include it when reporting unexpected behavior to SOLO. There's also a `GET /health` probe that returns `{"status": "healthy"}` if the service is up; point your uptime monitoring at it. **3. Verify your network access** The fastest end-to-end check of your token, membership, and `network_id` is an entity search scoped to your network: ```bash theme={null} curl -G https://api.solo.one/v1/entities/consumer_identity/search \ -H "Authorization: Bearer $SOLO_TOKEN" \ --data-urlencode "network_id=9f1c0c2e-3d5b-4a89-b1e2-7f6a8c0d4e21" \ --data-urlencode "last_name=Doe" \ --data-urlencode "limit=10" ``` ```json Response theme={null} [ { "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "identifier": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "created_at": "2026-05-12T18:21:43+00:00", "updated_at": "2026-05-12T18:21:43+00:00", "first_name": "Jane", "last_name": "Doe", "personal_email": "jane.doe@example.com", "date_of_birth": "1990-01-15", "social_security_number": "123-45-6789" } ] ``` A `200 OK` — even with an empty `[]` body — proves your token is valid and scoped to the network. `network_id` is the only required parameter; the rest narrow the search: * `first_name`, `last_name`, `personal_email` — case-insensitive **partial** matches. * `social_security_number`, `date_of_birth` — **exact** matches (`YYYY-MM-DD` for date of birth). * `limit` — maximum results, 1–100, default 20. ## Inspect the result Each element of the search response is a consumer core identity visible to you within the network: | Field | Meaning | | - | - | | `id` | The consumer's UUID. Use it as `consumer_identity_id` when linking a consent record to a known profile. | | `identifier` | The consumer record's stable string identifier. | | `created_at` / `updated_at` | When the consumer record was created and last modified. | | `first_name`, `last_name`, `personal_email`, `date_of_birth`, `social_security_number` | The core identity fields the record was matched on. Any of these may be `null` if not on file. | An empty array is a perfectly healthy response — it means no consumer in this network matches your filters yet. After you complete [Furnishing your first entity](/home/quickstart/furnish-an-entity), the consumer you furnish will appear here. Note what the search does *not* return: certificates, events, or any product data. Search is an identity lookup. Reading actual data about a consumer always goes through a product query with a consent record — that separation is the core of the platform's privacy model. ## The network tree As you scale, you'll encounter **child networks** — narrower trust boundaries nested under a parent network, such as the partner networks a sponsor bank runs for its fintechs. The network tree is explained in [Networks](/concepts/governance/networks), and the rules for who may change what are covered in [Network governance](/concepts/governance/network-governance). ## Troubleshooting > **401 — token expired or invalid** > > ```json theme={null} > { > "detail": "Authentication required", > "error_code": "AUTHENTICATION_REQUIRED" > } > ``` > > Your bearer token is missing, malformed, or expired. Tokens are issued by > your SOLO account manager — re-export `SOLO_TOKEN` with a fresh value and > confirm the header reads exactly `Authorization: Bearer $SOLO_TOKEN`. See > [Authentication](/home/authentication). > **422 — missing required parameter** > > ```json theme={null} > { > "detail": "query -> network_id: Field required" > } > ``` > > `network_id` is required on every search. A `422` always carries a single > `detail` string in `location -> field: message` form pointing at the first > invalid input — fix that field and retry. > **403 — role doesn't permit the operation** > > ```json theme={null} > { > "detail": "Permission denied", > "error_code": "PERMISSION_DENIED" > } > ``` > > Your token is valid but your role in this network doesn't allow the call — > for example, furnishing without the furnisher role. Confirm your roles with > your SOLO account manager. See > [Network roles](/concepts/governance/network-roles). > **200 with an empty array** > > Not an error. Your token and membership are fine; the network simply has no > consumer matching your filters. Partial-match fields (`first_name`, > `last_name`, `personal_email`) are the most forgiving way to probe for > data. ## Next steps With membership verified, pick the workflow that matches your role: ## In the dashboard ![Networks list — newly created network visible](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/networks/network-complete/05-create-success-list.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=e40a3e70913460ac01abf180cb32a499) ![Topbar network multiselect open](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/network-scope/network-complete/06-selector-open.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=2957930ead6c83adc1dcdc6558e4b561) ![Network detail — overview](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/networks/network-complete/03-detail-overview.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=76c1a4902b0efb62888375a5d7cb8e93) > **[Furnish your first entity](/home/quickstart/furnish-an-entity)** > > For furnishers — record consent and contribute KYC data. > **[Query your first product](/home/quickstart/query-a-product)** > > For queriers — read a consolidated certificate back. > **[Networks](/concepts/governance/networks)** > > Roles, the network tree, and policy usage in depth. > **[Network roles](/concepts/governance/network-roles)** > > What furnishers, queriers, and governors can each do. # Golden path: consuming network data Source: https://docs.solo.one/home/quickstart/network-consumption The reuse-first decision pattern — check what verified work exists, reuse it, and do only what is missing This golden path is the general **reuse-first** pattern for any consumer onboarding or re-verification: before you collect and verify from scratch, ask the network what reusable [trust assets](/concepts/trust/trust-assets) already exist, reuse them, and limit your own work to the gaps. It uses the consumer / KYC side; the business / KYB equivalent is [Business lending onboarding](/home/quickstart/business-lending-onboarding). ```mermaid theme={null} flowchart TB Find["Identify subject"] --> Consent["Record consent"] Consent --> Check["Coverage check: what exists?"] Check --> Exists{Reusable assets exist?} Exists -- yes --> Query["Query certificate · reuse"] Exists -- no --> Collect["Collect + verify yourself"] Query --> Partial{Complete for your policy?} Partial -- yes --> Done["Use in decisioning"] Partial -- no --> Collect Collect --> Furnish["Furnish your verification"] Furnish --> Done ``` ## Prerequisites > [!NOTE] > > * Network membership with the **querier** role (and **furnisher** role to > contribute), arranged with your SOLO account manager. > * A bearer token exported as `SOLO_TOKEN`. > * Your `network_id`, and the product's `product_id` for the coverage check. **1. Identify the subject** Find an existing profile so reuse and any later contribution consolidate on one consumer. ```bash theme={null} curl -G https://api.solo.one/v1/entities/consumer_identity/search \ -H "Authorization: Bearer $SOLO_TOKEN" \ --data-urlencode "network_id=9f1c0c2e-…" \ --data-urlencode "social_security_number=123-45-6789" ``` **2. Record consent** Capture the subject's permission and keep the `consent_id`. ```bash theme={null} curl -X POST https://api.solo.one/v1/consent/consumer_identity \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "network_id": "9f1c0c2e-…", "did_gather_consent_from_consumer_prior": true, "consumer_consent_event_identity": { "first_name": "Jane", "last_name": "Doe", "date_of_birth": "1990-01-15", "personal_email": "jane@example.com", "phone_number": "+14155550100", "social_security_number": "123-45-6789" } }' ``` **3. Ask what verified work already exists** The [coverage check](/api-overview/querying/coverage-check) is non-billable and answers the reuse question directly: *which furnishers already hold complete data for this subject and product?* ```bash theme={null} curl -X POST https://api.solo.one/v1/products/check \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "product_id": "b4a1d6e8-…", "consent_id": "5f3a8c21-…", "network_ids": ["9f1c0c2e-…"] }' ``` If any furnisher is `complete: true`, reusable trust assets exist — query them in the next step. If none are, skip to step 5 and do the work yourself. **4. Reuse: query the certificate** ```bash theme={null} curl -X POST https://api.solo.one/v1/products/kyc_certificate_snapshot/query \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "consent_id": "5f3a8c21-…", "network_ids": ["9f1c0c2e-…"], "policy_id": "5e7d2a14-…" }' ``` * **`200`** — reuse the consolidated certificate. Each populated sub-product carries its source's `furnishing_entity_id` and `attestation_event_id`, so the reuse is fully attributed. * **`204`** — nothing you're entitled to read satisfied the policy. Treat it as "do the missing work," not as an error. **5. Do only the missing work, then furnish it** Verify whatever the network couldn't supply, then contribute it back so the next participant reuses it — and so you earn [entitlement](/concepts/governance/entitlement) to read this subject later. ```bash theme={null} curl -X POST https://api.solo.one/v1/products/kyc_certificate_snapshot/furnish \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "network_id": "9f1c0c2e-…", "application_date": "2026-05-28", "records": [ { "first_name": "Jane", "last_name": "Doe", "date_of_birth": "1990-01-15", "social_security_number": "123-45-6789" } ] }' ``` ## The decision rule Every step exists to push work onto the network before it falls on you: 1. **Identify** so contributions consolidate, not fragment. 2. **Check** (free) before you **query** (billable). 3. **Reuse** on `200`; **complete only the gaps** on `204` or partial coverage. 4. **Furnish** what you verified, turning a one-off cost into a reusable asset. ## Related ## In the dashboard ![Networks list — newly created network visible](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/networks/network-complete/05-create-success-list.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=e40a3e70913460ac01abf180cb32a499) ![Topbar network multiselect open](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/network-scope/network-complete/06-selector-open.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=2957930ead6c83adc1dcdc6558e4b561) > **[Business lending onboarding](/home/quickstart/business-lending-onboarding)** > > The same pattern on the business / KYB side. > **[Network consumption](/api-overview/querying/overview)** > > Request anatomy, 200 vs 204, and the control plane. > **[Coverage check](/api-overview/querying/coverage-check)** > > The non-billable reuse pre-flight. > **[Trust assets](/concepts/trust/trust-assets)** > > What you're reusing and creating. # Querying your first product Source: https://docs.solo.one/home/quickstart/query-a-product Record consent and read consolidated data back from the network Querying reads consolidated data about an entity from one or more [networks](/concepts/governance/networks). Because you're reading personal data, every query rides on a [consent](/concepts/identity/consent) record: you create one (or reuse one), then pass its `consent_id` to the product's query endpoint. In this guide you'll query a consolidated KYC certificate for a consumer. ## Prerequisites > [!NOTE] > > Before you start, you need: > > * **Network membership with the querier role** — arranged with your SOLO > account manager (see > [Joining a network](/home/quickstart/join-a-network)). > * **A bearer token** (SDK token) exported as `SOLO_TOKEN`. See > [Authentication](/home/authentication). > * **Your `network_id`**. Optionally, the `policy_id` of a > [querying policy](/concepts/governance/querying-policies) — if you omit it, > each network's default policy applies. ## Consent, check, query **1. Create or reuse a consent record** If you already hold a valid `consent_id` for this consumer — for example from [Furnishing your first entity](/home/quickstart/furnish-an-entity) — reuse it and skip to the next step. Otherwise, create one: ```bash theme={null} curl -X POST https://api.solo.one/v1/consent/consumer_identity \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "network_id": "9f1c0c2e-3d5b-4a89-b1e2-7f6a8c0d4e21", "did_gather_consent_from_consumer_prior": true, "consumer_consent_event_identity": { "first_name": "Jane", "last_name": "Doe", "date_of_birth": "1990-01-15T00:00:00", "personal_email": "jane.doe@example.com", "phone_number": "5551234567", "social_security_number": "123-45-6789" } }' ``` ```json Response theme={null} { "consent_id": "5f3a8c21-09d4-4b7a-9d52-6e8b1f4c2a30", "field_access_grants": [ { "furnishing_entity_id": null, "field_definitions": [ "fraud_verification_event.confirmed_fraud_indicator", "fraud_verification_event.fraud_attribute_label", "fraud_verification_event.fraud_event_date", "fraud_verification_event.fraud_loss_event_category" ], "effective_from": "2026-06-09", "effective_to": null } ], "created_at": "2026-06-09T22:04:11+00:00", "events": "", "scope": "", "expires_at": null, "consented_fields": null } ``` Keep the `consent_id` — it identifies the consumer on every query while the consent remains valid. You can confirm it later with `GET /v1/consent/consumer_identity/{consent_id}?network_id=...`. > [!WARNING] > > `did_gather_consent_from_consumer_prior` is an attestation. Only send > `true` if you have actually obtained the consumer's permission; the API > rejects `false` with a `400`. **2. Optional: pre-flight coverage check** A query is a billable event. If you want to know whether the network holds enough data *before* you commit, run the non-billable coverage soft check. It reports per-furnisher field coverage for a product without producing a certificate: ```bash theme={null} curl -X POST https://api.solo.one/v1/products/check \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "product_id": "b4a1d6e8-2c7f-4e93-8a05-d9c3f1b7e642", "consent_id": "5f3a8c21-09d4-4b7a-9d52-6e8b1f4c2a30", "network_ids": ["9f1c0c2e-3d5b-4a89-b1e2-7f6a8c0d4e21"] }' ``` ```json Response theme={null} { "product_id": "b4a1d6e8-2c7f-4e93-8a05-d9c3f1b7e642", "consumer_identity_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "business_identity_id": null, "entities": [ { "furnishing_entity_id": "3e2d1c0b-9a87-4654-b321-0f9e8d7c6b5a", "furnishing_entity_name": "First Example Bank", "network_id": "9f1c0c2e-3d5b-4a89-b1e2-7f6a8c0d4e21", "complete": true, "models": [ { "model_name": "DocumentCaptureEvent", "met_count": 4, "total_count": 4, "fields": [ { "field_name": "is_document_captured", "met": true }, { "field_name": "document_capture_timestamp", "met": true }, { "field_name": "is_document_attribute_reviewed", "met": true }, { "field_name": "document_attribute_review_timestamp", "met": true } ] } ] } ] } ``` Any furnisher with `complete: true` can satisfy the product's data requirements for this consumer. See [Coverage check](/api-overview/querying/coverage-check) for the full semantics. **3. Query the KYC certificate** Pass the `consent_id` and your network scope to the product's query endpoint. `network_ids` takes one or more networks; the single optional `policy_id` is applied across every network in the list, pairing each network with that policy (omit it to use each network's default policy): ```bash theme={null} curl -X POST https://api.solo.one/v1/products/kyc_certificate_snapshot/query \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "consent_id": "5f3a8c21-09d4-4b7a-9d52-6e8b1f4c2a30", "network_ids": ["9f1c0c2e-3d5b-4a89-b1e2-7f6a8c0d4e21"], "policy_id": "5e7d2a14-8b3c-4f60-9e17-a2d4c6b8f015" }' ``` ```json Response theme={null} { "certificate_id": "8a6f4c2e-1b9d-4730-a5e8-3c7f0d2b6491", "provenance_event_id": "c1e8f5a3-7d20-4b96-8c44-e6a9b3d1f728", "consumer_identity_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "result": { "meta": { "network_id": "9f1c0c2e-3d5b-4a89-b1e2-7f6a8c0d4e21", "policy_id": "5e7d2a14-8b3c-4f60-9e17-a2d4c6b8f015" }, "document_capture": { "furnishing_entity_id": "3e2d1c0b-9a87-4654-b321-0f9e8d7c6b5a", "attestation_event_id": "f0a9b8c7-d6e5-4432-a1b0-c9d8e7f6a5b4", "assertions": { "document_capture_assertion": true, "document_capture_timestamp": "2026-04-15T00:00:00" }, "data": { "document_type": "us_passport", "document_number": "987654321", "document_issue_date": "2020-08-05", "document_expiration_date": "2030-08-05", "document_issuing_state": "NY", "document_capture_method": "mobile_live_capture" } }, "document_review": { "furnishing_entity_id": "3e2d1c0b-9a87-4654-b321-0f9e8d7c6b5a", "attestation_event_id": "f0a9b8c7-d6e5-4432-a1b0-c9d8e7f6a5b4", "assertions": { "document_attribute_review_assertion": true, "document_attribute_review_timestamp": "2026-04-16T00:00:00" }, "data": { "document_review_method": "automated_and_manual", "document_review_evidence_source": "document_issuer", "document_tamper_review_performed": true, "document_tamper_indicators_observed": false } }, "biometric_capture": { "furnishing_entity_id": "3e2d1c0b-9a87-4654-b321-0f9e8d7c6b5a", "attestation_event_id": "a7b6c5d4-e3f2-4109-b8a7-c6d5e4f3a2b1", "assertions": { "biometric_capture_assertion": true, "biometric_capture_timestamp": "2026-04-15T00:00:00" }, "data": { "biometric_capture_method": "live_selfie_video" } }, "biometric_review": null, "liveness_capture": null, "liveness_review": null, "address_capture": null, "address_verification": null, "identity_corroboration": null } } ``` To restrict the query to specific furnishers, add the optional `furnishing_entity_ids` list; when omitted, data from all furnishers is considered. ## 200 vs 204, and the X-Ref-Id header A query has two healthy outcomes: * **`200 OK`** — the available data satisfied the policy's requirements and a certificate was created. The body is the consolidated result above. * **`204 No Content`** — the query ran, but no certificate could be created: the data available to you didn't satisfy the policy requirements. The body is empty. Both outcomes carry an **`X-Ref-Id` response header** — the UUID of the recorded provenance event. On a `200` it equals the body's `provenance_event_id`; on a `204` it's the only identifier you get, so log it. Quote the `X-Ref-Id` when raising an issue with SOLO, and use it to reconcile query activity. > [!NOTE] > > A `204` is not an error and not necessarily "the network has nothing." It > means the fields *you* can read — bounded by > [entitlement](/concepts/governance/entitlement) and the querying > [policy](/concepts/governance/querying-policies) — were insufficient to > assemble a complete certificate. The coverage check in step 2 is the cheap > way to predict this. ## Inspect the result | Field | Meaning | | - | - | | `certificate_id` | UUID of the certificate created by this query. | | `provenance_event_id` | UUID of the recorded provenance event; matches the `X-Ref-Id` header. | | `consumer_identity_id` | UUID of the consumer the consent resolved to. | | `result.meta` | The anchor `network_id` the result is attributed to and the `policy_id` applied (`null` when the default policy was used). | | `result.document_capture` … `result.identity_corroboration` | The nine certificate sub-products: document capture/review, biometric capture/review, liveness capture/review, address capture/verification, and identity corroboration. Each populated sub-product carries the `furnishing_entity_id` and `attestation_event_id` of its source, an `assertions` block (what was asserted and when), and a `data` block with the underlying detail fields. | A sub-product is `null` when no furnisher's data satisfied it — which is expected, not a failure, as long as the policy doesn't require it. If a field you expected is missing, it's usually because you haven't earned entitlement to it (you've neither furnished nor previously queried it) or the policy doesn't expose it in this network. ## Maintaining the consent Reuse one `consent_id` for every query of the same consumer while the consent is valid. Its identity fields are immutable, but you can adjust `scope`, `expires_at`, and `consented_fields` as your agreement with the consumer evolves: ```bash theme={null} curl -X PUT https://api.solo.one/v1/consent/consumer_identity/5f3a8c21-09d4-4b7a-9d52-6e8b1f4c2a30 \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "network_id": "9f1c0c2e-3d5b-4a89-b1e2-7f6a8c0d4e21", "expires_at": "2027-01-01T00:00:00+00:00" }' ``` The response is the updated consent in the same shape as the create response. At least one of the three updatable fields must be present, and the `network_id` must match the network the consent was created in. To consent with different identity data, create a new record instead. ## Troubleshooting > **401 — token expired or invalid** > > ```json theme={null} > { > "detail": "Authentication required", > "error_code": "AUTHENTICATION_REQUIRED" > } > ``` > > Your bearer token is missing, malformed, or expired. Re-export `SOLO_TOKEN` > with a fresh token from your SOLO account manager. See > [Authentication](/home/authentication). > **400 — consent not gathered** > > ```json theme={null} > { > "detail": "Consent cannot be created without prior consumer consent gathering", > "error_code": "VALIDATION_ERROR" > } > ``` > > Returned by the consent create in step 1 when > `did_gather_consent_from_consumer_prior` is `false`. Gather the consumer's > permission first, then retry with `true`. > **404 — consent or consumer not resolvable** > > ```json theme={null} > { > "detail": "Consent record not found", > "error_code": "RESOURCE_NOT_FOUND" > } > ``` > > Two common causes: you looked up a consent with > `GET /v1/consent/consumer_identity/{consent_id}` using a `network_id` other than > the one it was created in (consents are network-scoped), or your query's > `consent_id` doesn't resolve to a known consumer. Verify the `consent_id` > and use the originating `network_id`. > **422 — missing required field** > > ```json theme={null} > { > "detail": "body -> network_ids: Field required" > } > ``` > > `network_ids` (with at least one entry) is required on every query, and > `consent_id` identifies the subject. A `422` carries a single `detail` > string in `location -> field: message` form pointing at the first invalid > input. > **204 — query ran but no certificate** > > Empty body, status `204`, with an `X-Ref-Id` header identifying the query > event. The data you're entitled to read didn't satisfy the policy. Run the > [coverage check](/api-overview/querying/coverage-check) to see which furnishers > and fields fall short, or revisit which networks and `policy_id` you're > querying with. ## Next steps ## In the dashboard ![Products catalogue — By Templates](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/02-products-catalogue-templates.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=9b7e2e0f75d8afdc2b2bcea42ec425b6) ![KYC product detail — overview tab](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/15-product-detail-overview.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=ecef698f86f8e2b15fc26af7aa144c3f) ![Run query opened from product detail](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/18-from-product-detail.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=ca7cbc708190bd67372c5f50aab6c6e4) ![Run query — choose entity](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/07-wizard-step-entity.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=09fdcfc66dd5fff1a13e762c7845d25d) ![Run query — networks and consent matrix](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/08-wizard-step-networks.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=b04d45e059f3fe51984e4064f68f23cc) ![Query completed — run detail overview](https://mintcdn.com/soloone/yGCbj3r-gv4V9x0i/images/dashboard/journeys/querying/query-full-wizard-consumer-kyc/09-wizard-success-detail.png?fit=max\&auto=format\&n=yGCbj3r-gv4V9x0i\&q=85\&s=cfccc007fd2f06d5eb61365bdbd2888a) > **[How querying works](/api-overview/querying/overview)** > > Network + policy + product + consumer, end to end. > **[Why consent is needed](/concepts/identity/consent)** > > The permission layer behind every query. > **[Coverage check](/api-overview/querying/coverage-check)** > > The non-billable soft check in depth. > **[Products](/api-overview/querying/products)** > > KYB certificates, screening lists, and the rest of the catalog. # Overview Source: https://docs.solo.one/home/welcome What the SOLO Network is, who participates in it, and where each kind of participant should start SOLO is **not a data aggregator**. It is a reusable verification network. A data provider answers *"what can we discover about this business or consumer?"* — and every institution starts that discovery from scratch. SOLO answers a different question: *"what has already been verified about this subject, who verified it, on what evidence, and can I safely reuse it?"* When one institution verifies a consumer or business, that completed work becomes a **reusable, permissioned, attributable result** that other participants can build on instead of repeating. > Data providers help you *verify* a business. SOLO helps you avoid *verifying > the same business again.* Banks and other institutions **furnish** verified data about consumers and businesses into networks they belong to, and **query** that data back — under explicit, auditable rules about who can read what, and why. Instead of integrating with dozens of verification vendors one at a time, you connect to SOLO once and work with standardized **products** — a KYC certificate, a KYB certificate, screening lists — inside a [network](/concepts/governance/networks) of participants you already trust. Every read is gated by the subject's [consent](/concepts/identity/consent), shaped by the network's policies, and limited to the fields you are entitled to see. ## Verify once, reuse everywhere In the traditional model, every application triggers a fresh round of verification. Three banks onboarding the same business each collect the same documents, run the same registry lookups, and trace the same ownership — paying again to rediscover the same facts. In a SOLO network, the first verification is preserved as a reusable result; later participants reuse it, with full provenance, and only do the work that is genuinely missing. ```mermaid theme={null} flowchart TB subgraph traditional [Traditional verification - repeat every time] direction LR A1[Application 1] --> V1["Verify identity, ownership, income"] --> D1[Decision] A2[Application 2] --> V2["Verify identity, ownership, income"] --> D2[Decision] A3[Application 3] --> V3["Verify identity, ownership, income"] --> D3[Decision] end subgraph solo [SOLO network - verify once and reuse] direction LR S1[Application 1] --> SV1["Verify once"] --> TA[("Reusable verified result")] S2[Application 2] --> TA S3[Application 3] --> TA TA --> SD["Decision faster"] end ``` See [How SOLO is different](/home/why-solo) for the full contrast with traditional data and KYB/KYC providers. ## How the network works Every interaction with the API is one of two operations on a product: 1. **Furnish** — contribute verified data about a [consumer or business](/concepts/identity/entities) into a network. Other participants can then query it, subject to the network's rules. 2. **Query** — read consolidated data about an entity from the network. A query requires a `consent_id` proving the subject agreed, and returns only the fields the network's policy exposes and you are [entitled](/concepts/governance/entitlement) to read. ```mermaid theme={null} flowchart LR You[Your system] -->|furnish| N[(SOLO Network)] You -->|query + consent| N Others[Other participants] -->|furnish| N N -->|consolidated result| You ``` The product catalog covers the core verification workload of a bank: **KYC certificates** consolidate identity-verification outcomes for consumers, **KYB certificates** do the same for businesses, and two **screening lists** — a bank-specific bad-actor list and a cross-bank financial-crimes watch list — answer targeted risk questions. Before committing to a billable query, you can run a non-billable coverage check (`POST /v1/products/check`) to see whether the network holds enough data about a subject to make the query worthwhile. The rules sit in the network itself: roles define what each participant *can* do, policies define what each product reads and writes, and entitlement defines what each participant *sees*. [High-level concepts](/home/overview) walks through the full model. The API itself is plain REST: JSON request and response bodies, standard HTTP status codes, and a single versioned prefix (`/v1`) for every public endpoint. Errors follow one consistent envelope — see [Errors](/home/errors) — so the handling code you write for your first endpoint works for all of them. ## Where to start Most organizations play one or more of three roles in a network. Find yours and follow its path. ### Network governors You operate a network: you decide which products it offers, what each product is allowed to read and write, and who participates. Start with [Networks](/concepts/governance/networks) to understand the trust boundary you are administering, then define the rules of the road with querying and furnishing policies — `POST /v1/networks/policies/querying` and `POST /v1/networks/policies/furnishing` create the policy shells, and the `PUT .../{policy_id}/configuration` endpoints author the per-field selections. The [Join a network](/home/quickstart/join-a-network) quickstart shows the participant side of what you are governing. ### Furnishers You contribute data: KYC or KYB outcomes your institution has already verified. Start with the [Furnishing overview](/api-overview/furnishing/overview) to learn how contributed data is validated and stored, then run the [Furnish an entity](/home/quickstart/furnish-an-entity) quickstart to push your first record through `POST /v1/products/kyc_certificate_snapshot/furnish`. High-volume furnishers can deliver [JSONL files](/api-overview/furnishing/file-upload) from the dashboard or by automated SFTP delivery instead of per-record API calls. ### Queriers You consume data: you want a consolidated KYC certificate or a screening-list answer for an entity you are onboarding. Start with the [Querying overview](/api-overview/querying/overview) to see how a query resolves across network, policy, and consent, and read [Consent](/concepts/identity/consent) — you must record consent and obtain a `consent_id` before any query. Then run the [Query a product](/home/quickstart/query-a-product) quickstart, which takes you from `POST /v1/consent/consumer_identity` to `POST /v1/products/kyc_certificate_snapshot/query` end to end. > [!TIP] > > Not sure which role you hold? Your network governor assigned it when your > organization joined. Many participants are both furnishers and queriers — the > two paths share the same concepts, so read both overviews. ## Environments The API is served per environment. All public endpoints sit under the `/v1` prefix and require a bearer token (see [Authentication](/home/authentication)). | Environment | Base URL | Purpose | | - | - | - | | Development | `https://api.dev.solo.one` | Early integration and experimentation | | Sandbox | `https://api.sandbox.solo.one` | Pre-production testing with test data | | Production | `https://api.prod.solo.one` | Live traffic | Two endpoints are available without authentication for monitoring: `GET /health` returns `{"status": "healthy"}`, and `GET /version` reports the supported API versions and build information. > [!NOTE] > > Credentials are scoped to a single environment. A token minted for sandbox > will not authenticate against production, and vice versa. Build against sandbox first: it mirrors production behavior — including consent enforcement, policy evaluation, and the full error model — without touching live data, so your integration is exercised end to end before you switch base URLs. ## Next steps > **[How SOLO is different](/home/why-solo)** > > Why SOLO is a reusable trust network, not a data aggregator. > **[Trust assets](/concepts/trust/trust-assets)** > > How a furnished verification becomes reusable, with provenance intact. > **[Quickstart](/home/quickstart)** > > Furnish an entity, query a product, and join a network — hands-on, in > minutes. > **[High-level concepts](/home/overview)** > > Networks, entities, consent, products, policies, and entitlement — and how > they fit together. > **[API Reference](/api-reference/introduction)** > > Endpoint-by-endpoint request and response schemas for the full `/v1` > surface. # How SOLO is different Source: https://docs.solo.one/home/why-solo Why SOLO is a reusable trust network, not a data aggregator — and how the architecture makes verified work reusable Most data and KYB/KYC vendors share one shape: they aggregate from external sources and hand each institution a fresh answer. SOLO has a different shape: it **preserves completed verification work** and lets the next institution reuse it, with provenance intact. This page makes that distinction concrete. ## Two architectures A traditional provider sits between external sources and the institution. Every institution that asks a question pays to rediscover the same facts. ```mermaid theme={null} flowchart LR SoS[Secretary of State] --> Vendor IRS[IRS] --> Vendor Watch[Watchlists] --> Vendor Web[Web data] --> Vendor[Data provider] Vendor --> Inst["Institution (starts from scratch every time)"] ``` SOLO sits *between institutions*. The institution that does the verification creates a reusable result; the network preserves it, and other participants reuse it under permission. ```mermaid theme={null} flowchart LR A["Institution A"] --> Verify["Verifies the subject"] Verify --> Result[("Reusable verified result + provenance")] Result --> Network[(SOLO Network)] Network --> B["Institution B reuses it"] Network --> C["Institution C reuses it"] Verify -.retains attribution + control.-> A ``` This is not better data aggregation. It is **trust portability**: the verification stops dying at the end of a single application. ## The one-sentence difference The same idea, said a few ways: * Data providers help you *verify* a business. SOLO helps you avoid *verifying the same business again*. * Providers *sell information*. SOLO makes *verified work reusable*. * Aggregators answer *"what data exists?"* SOLO answers *"what trust already exists?"* ## Side by side | Traditional data / KYB / KYC providers | SOLO | | - | - | | Discover information | Reuse verified information | | Aggregate external sources | Preserve completed verification work | | Every institution verifies again | Participants reuse prior verification | | Vendor owns the intelligence | Network participants create the intelligence | | Point-in-time lookup | Persistent, queryable verification | | More applications create more work | More applications create more reuse | | You only pay for data | You contribute *and* benefit from verified work | ## Every claim above maps to a real mechanism This is not aspirational positioning. Each contrast is backed by a feature that exists in the API today: | Claim | How SOLO actually does it | | - | - | | "Reuse verified information" | A [product](/api-overview/querying/products) query *consolidates* verification events furnished by participants into a single [KYC](/api-overview/querying/kyc-certificate-snapshot) or [KYB](/api-overview/querying/kyb-certificate-snapshot) certificate, instead of re-collecting the underlying evidence. | | "Preserve completed verification work" | Furnished work is stored as attested verification events and re-resolved on every future query — see [Trust assets](/concepts/trust/trust-assets). | | "Attributable" | Every certificate sub-product carries the `furnishing_entity_id` and `attestation_event_id` of its source — see [Provenance](/concepts/trust/provenance). | | "Permissioned" | Reads require the subject's [consent](/concepts/identity/consent), and fields are filtered by your [entitlement](/concepts/governance/entitlement). | | "How fresh must it be to count?" | [Querying policies](/concepts/governance/querying-policies) enforce freshness windows; data outside the window is not reused (a `204`, not a stale answer). | | "Contribute *and* benefit" | Furnishing earns the [entitlement](/concepts/governance/entitlement) that lets you read an entity back later — contribution and consumption are the same loop. | | "Know before you pay" | The non-billable [coverage check](/api-overview/querying/coverage-check) answers *"does reusable verified work already exist for this subject?"* before a billable query. | ## What SOLO is not To avoid the wrong takeaway: * **Not another KYB/KYC workflow tool.** SOLO does not replace your verification vendors with one more; it makes the verifications already done reusable across the network. * **Not an aggregator with permissions bolted on.** The unit of value is a *reusable, attributed verification*, not a fresh external lookup. * **Not a black box you must trust blindly.** The architecture is exposed on purpose — provenance, consent, entitlement, and policy are all visible, because visibility is what makes participants comfortable reusing each other's work. ## Where to go next > **[Trust assets](/concepts/trust/trust-assets)** > > How a furnished verification becomes something reusable. > **[Provenance](/concepts/trust/provenance)** > > Who verified what, when, and on what evidence. > **[High-level concepts](/home/overview)** > > Networks, entities, consent, products, policies, entitlement. # Bank-Specific Bad Actor List Source: https://docs.solo.one/products/bank-specific-bad-actor-list Sponsor-bank-scoped screening for repeat policy violators The **Bank-Specific Bad Actor List** is a sponsor-bank-scoped list of specific individuals who, as prior customers of one or more of the bank's fintech partners, violated one or more documented bank policies. It answers a narrow question — *is this consumer on this bank's bad actor list?* — and returns a listing indicator plus a small set of context fields. It never issues a reusable certificate; it is a point-in-time check. | | | | - | - | | **Category** | Fraud & Financial Crime | | **Use case** | Bad actor screening | | **Subject** | Consumer | | **Family** | List (query-only) | | **Scope** | Your sponsor bank's network only — results never cross network boundaries | | **Operation** | `POST /v1/products/bank_specific_bad_actor_list/query` | Because it is scoped to one sponsor bank, a consumer flagged in Bank A's network is invisible to Bank B. The events behind the list are contributed by network participants through governed furnishing flows. ## At a glance ```bash theme={null} curl -X POST https://api.solo.one/v1/products/bank_specific_bad_actor_list/query \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "consent_id": "a3f0b9c7-…", "network_ids": ["9f1c0c2e-…"] }' ``` ```json theme={null} { "provenance_event_id": "7b2d9c4e-…", "consumer_identity_id": "c2a4e8d0-…", "furnishing_entity_id": null, "is_listed": false, "bad_actor_list_placement_date": null, "bad_actor_list_reason_code": null } ``` A clean result is `"is_listed": false` (always `200 OK`, never `204`). When the consumer is listed, `is_listed` is `true` and the context fields are populated with a placement date and a reason code. ## Matching & fields Subjects are matched on the consumer identity behind your [consent](/concepts/identity/consent) record. SSN and date of birth are mandatory match inputs; name, phone, and email refine the match. Reason codes are drawn from a documented set — `account_abuse`, `fraud`, and `policy_violation` — and each listing carries the bank's definition of the violated policy in `bad_actor_reason_definition`. | Field | API name | Type | | - | - | - | | Bad Actor Reason Code | `bad_actor_reason_code` | String | | Bad Actor Reason Definition | `bad_actor_reason_definition` | String | | Bad Actor List Placement Date | `bad_actor_list_placement_date` | Date | > **[Full field reference & matching rules](/api-overview/querying/screening-lists)** > > The screening lists deep dive — match inputs, per-list event fields, reason > codes, and compliance posture. ## Related > **[Products](/products/overview)** > > The full product catalog. > **[Cross-Bank Financial Crimes Watch List](/products/cross-bank-financial-crimes-watch-list)** > > The cross-bank counterpart, visible across the consortium. # Confirmed Fraud Attribute List Source: https://docs.solo.one/products/confirmed-fraud-attribute-list Consortium screening for identifying attributes tied to confirmed fraud The **Confirmed Fraud Attribute List** is a consortium for banks and fintechs to furnish discrete personal identifying attributes tied to **confirmed** fraud events. Rather than re-investigating a subject from scratch, a query checks whether the consumer is associated with a confirmed fraud event and surfaces the specific identifying attributes that were implicated. It never issues a reusable certificate; it is a point-in-time check. | | | | - | - | | **Category** | Fraud & Financial Crime | | **Use case** | Bad actor screening | | **Subject** | Consumer | | **Family** | List (query-only) | | **Operation** | `POST /v1/products/confirmed_fraud_attribute_list/query` | The distinction from the other lists is its focus on **attributes**: each listing ties a confirmed fraud event to discrete identifying data (the `fraud_attribute_label` and its content), so consumers of the list learn not just *that* fraud occurred but *which* identity attributes were involved. The events behind it are contributed by network participants through governed furnishing flows. ## At a glance ```bash theme={null} curl -X POST https://api.solo.one/v1/products/confirmed_fraud_attribute_list/query \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "consent_id": "a3f0b9c7-…", "network_ids": ["9f1c0c2e-…"] }' ``` A clean result is `"is_listed": false` (always `200 OK`, never `204`). When the consumer is tied to a confirmed fraud event, the attributes from the product's `FraudVerificationEvent` schema are populated: ```json theme={null} { "provenance_event_id": "7b2d9c4e-…", "consumer_identity_id": "c2a4e8d0-…", "furnishing_entity_id": "e1d2c3b4-…", "is_listed": true, "confirmed_fraud_indicator": true, "fraud_attribute_label": "phone_number", "fraud_event_date": "2025-12-02", "fraud_loss_event_category": "account-takeover", "fraud_malicious_intent_method": "phishing" } ``` ## Matching & fields Subjects are matched on the consumer identity behind your [consent](/concepts/identity/consent) record. SSN and date of birth are mandatory match inputs; name, phone, and email refine the match. The fraud attributes are defined by the product's `FraudVerificationEvent` schema. *Field* is the display name and *API name* is the `field_name` used in policies. | Field | API name | Type | | - | - | - | | Confirmed Fraud Indicator | `confirmed_fraud_indicator` | Boolean | | Fraud Attribute Label | `fraud_attribute_label` | String | | Fraud Attribute ID | `fraud_attribute_id` | String | | Fraud Attribute Content | `fraud_attribute_content` | String | | Fraud Event ID | `fraud_event_id` | String | | Fraud Event Date | `fraud_event_date` | Date | | Fraud Event Evidence Type | `fraud_event_evidence_type` | String | | Fraud Event Evidence Type Other Specification | `fraud_event_evidence_type_other` | String | | Fraud Event Evidence Description | `fraud_event_evidence_description` | String | | Fraud Loss Event Category | `fraud_loss_event_category` | String | | Fraud Loss Event Documentation Upload | `fraud_loss_event_documentation_upload` | String | | Fraud Malicious Intent Method | `fraud_malicious_intent_method` | String | | Fraud Malicious Intent Lineage Documentation Upload | `fraud_malicious_intent_lineage_documentation_upload` | String | Loss-event categories include `financial-theft`, `account-takeover`, and `synthetic-identity`; malicious-intent methods include `phishing` and `card-not-present`. A network's [querying policy](/concepts/governance/querying-policies) can restrict which categories and methods a query considers. > [!WARNING] > > Confirmed-fraud signals are screening inputs, not adjudications. Whether a > listing can support adverse action depends on your program's compliance > framework. Route hits to your compliance review process. ## Related > **[Products](/products/overview)** > > The full product catalog. > **[Bank-Specific Bad Actor List](/products/bank-specific-bad-actor-list)** > > Sponsor-bank-scoped repeat-offender screening. # Coverage Check Source: https://docs.solo.one/products/coverage-check Non-billable pre-flight of field coverage before running a query The **coverage check** is a non-billable, read-only pre-flight for product queries. Given a product, a subject, and a network + policy scope, it reports — **per furnisher** — which of the policy's required fields are covered by data already furnished to the network. It answers *"if I run this query now, can it succeed?"* without issuing a certificate, recording a query event, or creating a billable event. | | | | - | - | | **Category** | Utility (not a product query) | | **Use case** | Pre-flight a query, avoid predictable `204`s | | **Subject** | Consumer or business (matches the checked product) | | **Family** | Non-billable check | | **Operation** | `POST /v1/products/check` | One endpoint serves every product: pass the `product_id` of the product you intend to query. ## Why it exists A product query that resolves but cannot satisfy the policy returns [`204 No Content`](/api-overview/querying/overview#200-vs-204-here-is-data-vs-nothing-usable) — and that resolved query is still a billable event. The coverage check lets you avoid predictable `204`s for free. ```mermaid theme={null} sequenceDiagram participant App as Your app participant API as SOLO API App->>API: POST /v1/products/check (non-billable) API-->>App: per-furnisher coverage alt coverage complete App->>API: POST /v1/products/{product}/query (billable) API-->>App: 200 + certificate else coverage incomplete App->>App: skip query / adjust UX / pick another policy end ``` ## At a glance ```bash theme={null} curl -X POST https://api.solo.one/v1/products/check \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "product_id": "3e8a1d5f-…", "consent_id": "a3f0b9c7-…", "policy_id": "5e7d2a14-…", "network_ids": ["9f1c0c2e-…"] }' ``` A `200 OK` returns one entry per furnisher that has relevant data, each with a `complete` flag and a per-model, per-field breakdown of which requirements are `met`. The `field_name` values trace directly to each product's field reference. > [!WARNING] > > The coverage check is a *soft* check. It does not issue a certificate, return > subject data, record a query event, or guarantee the subsequent query returns > `200` — data can change between the check and the query. > **[Full request & response schema](/api-overview/querying/coverage-check)** > > The complete coverage check deep dive — full request fields, the response > shape, and when to use it. ## Related > **[Products](/products/overview)** > > The full product catalog. > **[Querying](/api-overview/querying/overview)** > > The billable query the coverage check pre-flights. # Cross-Bank Financial Crimes Watch List Source: https://docs.solo.one/products/cross-bank-financial-crimes-watch-list 314(b) consortium screening for inter-bank financial-crimes signals The **314(b) Cross-Bank Financial Crimes Watch List** is a cross-bank shared consortium that extends beyond one sponsor-bank network and focuses on sharing suspicious financial-crimes activity. It is governed by banks as defined by 31 CFR 1020.100(d), under 314(b) permissible purpose. It answers — *is this consumer associated with suspicious financial-crimes activity reported across participating banks?* — returning a listing indicator plus a small set of context fields. It never issues a reusable certificate. | | | | - | - | | **Category** | Fraud & Financial Crime | | **Use case** | Watch list screening | | **Subject** | Consumer | | **Family** | List (query-only) | | **Scope** | All participating banks — visibility spans the consortium | | **Operation** | `POST /v1/products/cross_bank_financial_crimes_watch_list/query` | Unlike the [Bank-Specific Bad Actor List](/products/bank-specific-bad-actor-list), this list is cross-bank by design: it exists so that suspicious financial-crimes activity observed at one bank is visible to the others, under the 314(b) information-sharing framework. The events behind it are contributed by network participants through governed furnishing flows. ## At a glance ```bash theme={null} curl -X POST https://api.solo.one/v1/products/cross_bank_financial_crimes_watch_list/query \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "consent_id": "a3f0b9c7-…", "network_ids": ["9f1c0c2e-…"] }' ``` ```json theme={null} { "provenance_event_id": "7b2d9c4e-…", "consumer_identity_id": "c2a4e8d0-…", "furnishing_entity_id": null, "is_listed": false, "entity_level_adverse_action_eligible_indicator": null, "watch_list_placement_date": null } ``` A clean result is `"is_listed": false` (always `200 OK`, never `204`). When the consumer is listed, the context fields — including a placement date and the `entity_level_adverse_action_eligible_indicator` — are populated. ## Matching & fields Subjects are matched on the consumer identity behind your [consent](/concepts/identity/consent) record. SSN and date of birth are mandatory match inputs; name, phone, and email refine the match. Event categories include `money_laundering`, `terrorist_financing`, and `fraud`; signal levels are `high`, `medium`, or `low`. A network's [querying policy](/concepts/governance/querying-policies) can restrict which categories and signal levels a query considers — for example, only `high`-signal events. | Field | API name | Type | | - | - | - | | Event Date | `event_date` | Date | | Event Category | `event_category` | String | | Signal Level | `signal_level` | String | > [!WARNING] > > Watch-list signals are screening inputs, not adjudications. Whether a listing > can support adverse action depends on your program's compliance framework — > this list operates under 314(b) permissible purpose, and the > `entity_level_adverse_action_eligible_indicator` exists precisely because not > every signal qualifies. Route hits to your compliance review process. > **[Full field reference & matching rules](/api-overview/querying/screening-lists)** > > The screening lists deep dive — match inputs, per-list event fields, and > compliance posture. ## Related > **[Products](/products/overview)** > > The full product catalog. > **[Bank-Specific Bad Actor List](/products/bank-specific-bad-actor-list)** > > The sponsor-bank-scoped counterpart. # KYB Certificate Source: https://docs.solo.one/products/kyb-certificate-snapshot Reusable business-verification certificate for business onboarding The **KYB Certificate** is a reusable Know Your Business attestation that packages UBO (ultimate beneficial owner), incorporation, and business-identity evidence into a single certificate. Rather than each institution repeating registry lookups, ownership tracing, and risk screening for the same business, a query assembles the verification work already furnished by network participants into one network-issued result. | | | | - | - | | **Category** | Identity Verification | | **Use case** | Customer onboarding | | **Subject** | Business | | **Family** | Certificate (issues a `certificate_id`) | | **Operations** | `POST /v1/products/kyb_certificate_snapshot/query`, `POST /v1/products/kyb_certificate_snapshot/furnish` | ## What's in the certificate A KYB certificate consolidates three **sub-products**, each a block in the query response with its own `assertions` (what was attested, and when) and `data` (the supporting attributes): | Sub-product | Response key | What it attests | | - | - | - | | Business identity verification | `business_identity_verification` | The legal entity exists and is what it claims — registration, jurisdiction, tax ID validation, operational existence. | | Ownership & control verification | `business_ownership_control_verification` | Beneficial owners, control persons, and authorized representatives were identified and evidenced. | | Risk & compliance assessment | `business_risk_compliance_assessment` | Sanctions, adverse media, restricted-activity, and activity-risk screening were performed. | Every populated sub-product carries the `furnishing_entity_id` of the participant whose data backed it and the `attestation_event_id` of their attestation. ## At a glance ```bash theme={null} curl -X POST https://api.solo.one/v1/products/kyb_certificate_snapshot/query \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "consent_id": "a3f0b9c7-…", "policy_id": "5e7d2a14-…", "network_ids": ["9f1c0c2e-…"] }' ``` A `200 OK` means a certificate was issued — the response carries a `certificate_id`, the billable `provenance_event_id`, and a `result` block with each sub-product (sub-products without qualifying data are `null`). A `204 No Content` means the available data did not satisfy the policy. > **[Full field reference & response schema](/api-overview/querying/kyb-certificate-snapshot)** > > The complete KYB certificate deep dive — every model, the full response > example, how it resolves per network, and the `204` rules. ## Related > **[Products](/products/overview)** > > The full product catalog. > **[Coverage check](/products/coverage-check)** > > Check field coverage before running a billable query. # KYC Certificate Source: https://docs.solo.one/products/kyc-certificate-snapshot Reusable consumer identity-verification certificate for onboarding The **KYC Certificate** is a reusable identity-verification certificate for consumer onboarding. Instead of each institution re-running document capture, biometric checks, and identity corroboration for the same person, a query assembles the verification work already furnished by network participants into a single network-issued certificate. | | | | - | - | | **Category** | Identity Verification | | **Use case** | Customer onboarding | | **Subject** | Consumer | | **Family** | Certificate (issues a `certificate_id`) | | **Operations** | `POST /v1/products/kyc_certificate_snapshot/query`, `POST /v1/products/kyc_certificate_snapshot/furnish` | ## What's in the certificate A KYC certificate consolidates up to nine **sub-products**, each a block in the query response with its own `assertions` (what was attested, and when) and `data` (the supporting attributes): | Sub-product | Response key | What it attests | | - | - | - | | Document capture | `document_capture` | An identity document was captured, with its type, number, and dates. | | Document review | `document_review` | The document's attributes were reviewed — tamper and machine-readable-data checks. | | Biometric capture | `biometric_capture` | A biometric artifact (e.g. a selfie) was captured. | | Biometric review | `biometric_review` | The biometric was compared against a reference, with quality and outcome. | | Liveness capture | `liveness_capture` | Liveness evidence was captured. | | Liveness review | `liveness_review` | The liveness evidence was reviewed, with outcome and confidence tier. | | Address capture | `address_capture` | A residential address was captured. | | Address verification | `address_verification` | The address was verified, with methods and per-source match counts. | | Identity corroboration | `identity_corroboration` | CIP-style corroboration — SSN, name, and DOB matches across sources. | Every populated sub-product carries the `furnishing_entity_id` of the participant whose data backed it and the `attestation_event_id` of their attestation, so the certificate is auditable down to its sources. ## At a glance ```bash theme={null} curl -X POST https://api.solo.one/v1/products/kyc_certificate_snapshot/query \ -H "Authorization: Bearer $SOLO_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "consent_id": "a3f0b9c7-…", "policy_id": "5e7d2a14-…", "network_ids": ["9f1c0c2e-…"] }' ``` A `200 OK` means a certificate was issued — the response carries a `certificate_id`, the billable `provenance_event_id`, and a `result` block with each requested sub-product (sub-products the policy didn't require, or that lacked data, are `null`). A `204 No Content` means the available data did not satisfy the policy. > **[Full field reference & response schema](/api-overview/querying/kyc-certificate-snapshot)** > > The complete KYC certificate deep dive — every model, the full response > example, how it resolves per network, and the `204` rules. ## Related > **[Products](/products/overview)** > > The full product catalog. > **[Coverage check](/products/coverage-check)** > > Check field coverage before running a billable query. # Products Source: https://docs.solo.one/products/overview The standardized data sets you query and furnish on the SOLO network A **product** is a standardized, named data set you can **query** (read) and — for certificate products — **furnish** (write) through the network. Every product gives all participants a common schema for one verification domain, so data furnished by one participant can be queried by another without bespoke integration. This tab is the high-level outline of each product. For request anatomy, `200` vs `204` semantics, billing, and the `X-Ref-Id` header, see [Network consumption](/api-overview/querying/overview). ## The catalog SOLO offers **five products**, in two categories. The exact set available to you depends on the [networks](/concepts/governance/networks) you belong to. | Product | Category | Subject | What it answers | | - | - | - | - | | [KYC Certificate](/products/kyc-certificate-snapshot) | Identity Verification | Consumer | "Has this consumer's identity been verified — documents, biometrics, liveness, address, and corroboration?" | | [KYB Certificate](/products/kyb-certificate-snapshot) | Identity Verification | Business | "Has this business been verified — identity, ownership & control, and risk/compliance?" | | [Bank-Specific Bad Actor List](/products/bank-specific-bad-actor-list) | Fraud & Financial Crime | Consumer | "Has this consumer violated a documented policy of this sponsor bank's network?" | | [Cross-Bank Financial Crimes Watch List](/products/cross-bank-financial-crimes-watch-list) | Fraud & Financial Crime | Consumer | "Is this consumer associated with suspicious financial-crimes activity reported across participating banks?" | | [Confirmed Fraud Attribute List](/products/confirmed-fraud-attribute-list) | Fraud & Financial Crime | Consumer | "Is this consumer tied to a confirmed fraud event, and which identifying attributes were implicated?" | ## Two families | Behavior | Certificates | Lists | | - | - | - | | Examples | KYC Certificate, KYB Certificate | Bad Actor List, Financial Crimes Watch List, Confirmed Fraud Attribute List | | What you get | A consolidated, multi-attribute verification result | A listing indicator plus a small set of context fields | | Furnishable via API | Yes — `/furnish` | No (query-only) | | Issues an artifact | Yes — `certificate_id` | No | | Empty result | `204 No Content` | `200` with `is_listed: false` | | Billable query event | Yes | Yes | * **Certificates** are consolidated verification results assembled from data furnished by network participants. Querying one can *issue* a reusable certificate for the subject, and returns `204 No Content` when the available data cannot satisfy the policy. * **Lists** are read-only yes/no checks against flagged individuals. They always return `200 OK`, with a clean result expressed as `"is_listed": false`. ## The query / furnish pattern > **Query** > > **Read** consolidated data for an entity, drawn from what authorized > participants have furnished. Requires a [consent](/concepts/identity/consent) > ID or a direct profile reference. > **Furnish** > > **Contribute** verified data for an entity into a network, making it > available for future queries by entitled participants. ```text theme={null} POST /v1/products/{product}/query POST /v1/products/{product}/furnish (certificate products) ``` There is also one utility endpoint that is **not** a product query: `POST /v1/products/check`, the non-billable [coverage check](/products/coverage-check), which reports whether the furnished data for a subject can satisfy a policy *before* you run a billable query. ## How products relate to networks and policies ```mermaid theme={null} flowchart LR Prod[Product
schema] --> NP[Offered in a network] Net[Network] --> NP NP --> Pol[Querying policy
field rules + freshness] Pol --> Q[Query result] ``` A product defines *what* data exists — its models, fields, and types. A [network](/concepts/governance/networks) decides *which* products it offers to its members. A [querying policy](/concepts/governance/querying-policies) defines, per network, *which parts* of a product a query may read and under what conditions. The same product can behave differently in two networks because each network attaches its own policies. ## Choosing a product | If you need to… | Use | | - | - | | Verify a consumer's identity at onboarding | [KYC Certificate](/products/kyc-certificate-snapshot) | | Verify a business and its beneficial owners | [KYB Certificate](/products/kyb-certificate-snapshot) | | Screen a consumer against your sponsor bank's network history | [Bank-Specific Bad Actor List](/products/bank-specific-bad-actor-list) | | Screen a consumer for cross-bank financial-crimes signals | [Cross-Bank Financial Crimes Watch List](/products/cross-bank-financial-crimes-watch-list) | | Screen a consumer against confirmed-fraud attributes | [Confirmed Fraud Attribute List](/products/confirmed-fraud-attribute-list) | | Know whether a query can succeed before paying for it | [Coverage Check](/products/coverage-check) | ## Product deep dives > **[KYC Certificate](/products/kyc-certificate-snapshot)** > > Consolidated consumer identity verification — up to nine sub-products. > **[KYB Certificate](/products/kyb-certificate-snapshot)** > > Business identity, ownership & control, and risk/compliance in one > certificate. > **[Bank-Specific Bad Actor List](/products/bank-specific-bad-actor-list)** > > Sponsor-bank-scoped repeat-offender screening. > **[Cross-Bank Financial Crimes Watch List](/products/cross-bank-financial-crimes-watch-list)** > > 314(b) consortium financial-crimes signals. > **[Confirmed Fraud Attribute List](/products/confirmed-fraud-attribute-list)** > > Discrete identifying attributes tied to confirmed fraud events. > **[Coverage Check](/products/coverage-check)** > > Non-billable pre-flight: can this query succeed under this policy?