Skip to content
Datasource Background Check Partner API

API reference: Datasource Background Check Partner API

Every operation and webhook, generated from the OpenAPI document (/openapi/v1.json), the same source as the interactive reference. Every operation needs your API key (X-Api-Key: pk_test_YOUR_KEY or Authorization: Bearer pk_test_YOUR_KEY). Error bodies are problem details with a stable code: see the error catalog.

Account

Check that your API key works.

GET /status

Check your API key (operationId CheckApiKey)

Returns 200 with a short message when the API key is valid. Useful for verifying setup and key rotation.

Responses:

  • 200: The key is valid. The body is plain text.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

Settings

Your webhook URL, the return URLs the connect page may send a browser back to, and your webhook signing secret.

GET /settings

Get your settings (operationId GetSettings)

Returns the webhook URL and the connect return URLs you registered, and how many years a completed check stays valid.

Responses:

  • 200: Your registered webhook URL and connect return URLs.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

200 example (application/json):

{
  "webhookUrl": "https://app.example.com/cbc/webhooks",
  "connectReturnUrls": [
    "https://app.example.com/cbc/connect-done"
  ]
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

PUT /settings

Update your settings (operationId UpdateSettings)

Registers where we send webhooks and which return URLs the connect page may send a browser back to.

webhookUrl must be an https:// URL whose host resolves only to public addresses; it is checked again whenever a webhook is sent. Send an empty webhookUrl to stop webhooks (poll instead). connectReturnUrls holds up to 10 https:// URLs. A returnUrl in POST /connect/sessions must match one of them exactly. This call replaces both values; send the complete set.

checkValidityYears (1 to 5, default 2) is how long a CLEAR or REVIEW check stays valid: its expirationDate is its completion plus that many years, and until then ordering the same applicant again needs forceReorder. Omit it to keep the current value. A change applies to checks completed after it.

Request body (application/json): SettingsRequest

Field Type Required Description
webhookUrl string (uri) | null no https:// URL that receives webhooks. Its host must resolve only to public addresses. Empty or omitted turns webhooks off.
connectReturnUrls string (uri)[] (≤ 10 items) | null no Up to 10 https:// URLs the connect page may send a browser back to. A returnUrl must match one exactly.
checkValidityYears integer (int32; 1 to 5) | null no Your account's default for how many years a CLEAR or REVIEW check stays valid: its expirationDate is its completion plus this. A whole number from 1 to 5. A client's own setting (PUT /clients/{clientRef}/settings) overrides it. Omitted keeps the current value (default 2). Applies to checks completed after the change.

Request example:

{
  "webhookUrl": "https://app.example.com/cbc/webhooks",
  "connectReturnUrls": [
    "https://app.example.com/cbc/connect-done"
  ]
}

Responses:

  • 200: The saved settings. Both values were replaced with what you sent.
  • 400: validation_failed: a URL isn't https://, has credentials or a fragment, or its host resolves to a private address, or there are more than 10 return URLs. Nothing was changed.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

200 example (application/json):

{
  "webhookUrl": "https://app.example.com/cbc/webhooks",
  "connectReturnUrls": [
    "https://app.example.com/cbc/connect-done"
  ]
}

400 example validation_failed (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#validation_failed",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "webhookUrl": [
      "webhookUrl must use https://."
    ]
  },
  "code": "validation_failed"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

GET /settings/webhook-deliveries

List recent webhook deliveries (operationId ListWebhookDeliveries)

Every attempt to deliver a webhook to your endpoint in the last 30 days, newest first: the event, its eventId, the check (for check events), your endpoint's HTTP status and how long it took. Use it to see what was sent and how your endpoint answered. Filter with eventId or checkId; limit is 1 to 200 (default 50). Bodies are never stored, ours or yours: re-send a check's current state with POST /clients/{clientRef}/checks/{checkRef}/replay-webhook.

Parameters:

Name In Type Required Description
limit query integer (int32) no Page size, 1 to 500 (larger values are clamped). Defaults to 100.
eventId query string no
checkId query string no

Responses:

  • 200: OK
  • 400: Bad Request
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

400 example (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#validation_failed",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "level": [
      "level is required."
    ]
  },
  "code": "validation_failed"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

POST /settings/webhook-secret

Rotate your webhook secret (operationId RotateWebhookSecret)

Creates a new webhook signing secret and returns it. It is shown only once; store it before switching your signature check over. The old secret stops working immediately, so webhooks sent before you switch will fail verification until you do.

Responses:

  • 200: The new secret, shown only this once. The old one stopped working immediately.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

200 example (application/json):

{
  "webhookSecret": "whsec_YOUR_SECRET"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

POST /settings/webhook-test

Send a test webhook (operationId TestWebhook)

Sends a signed ping event to your webhook URL, so you can verify your endpoint and your signature check. Delivery is asynchronous and retried like any other webhook.

Responses:

  • 202: A signed ping was queued for delivery to your webhook URL.
  • 400: validation_failed: no webhook URL is registered. Register one with PUT /settings first.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

202 example (application/json):

{
  "status": "QUEUED"
}

400 example validation_failed (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#validation_failed",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "webhookUrl": [
      "Register a webhookUrl with PUT /settings first."
    ]
  },
  "code": "validation_failed"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

Connect

The hosted connect page: a church enters its own details and billing on a page we host, so you never see card data or passwords.

POST /connect/sessions

Create a connect session (operationId CreateConnectSession)

Starts the hosted connect page for one church. Call this from your server when the church's admin clicks your "Connect" button, then send their browser to the returned connectUrl (a normal top-level navigation; no iframe). The link works once and must be opened within 15 minutes.

The church enters its details and billing (card or invoice) on our page, so you never see card data or passwords. When it finishes we send the browser back to your returnUrl with session_id and state (completed, cancelled or migration_requested) and no secrets, and send a client.connected webhook. Confirm with the webhook or GET /connect/sessions/{sessionId} before treating the church as connected.

returnUrl must exactly match one of your registered connectReturnUrls (PUT /settings). 409 if externalClientId is already an active client.

The church's primary contact gets an email from Datasource with their sign-in and a temporary password (changed at first sign-in). Submitting the page twice is safe: the second submit is told the account is being created, and only one account is ever made. Invoice billing uses the billing setup copied from your account plus the church's billing contact; a card is entered by the church on our page and never reaches you.

Request body (application/json): ConnectSessionRequest

Field Type Required Description
externalClientId string (≤ 100 chars) yes Your own id for the church (max 100 characters). It becomes the client's externalClientId.
churchName string (≤ 200 chars) yes The church's name (max 200 characters), shown read-only on the page.
prefill object | null | ConnectPrefill no Optional details to pre-fill on the contact step.
returnUrl string (uri) yes Where to send the browser when the church is done. Must exactly match one of your registered connectReturnUrls (see PUT /settings). We add session_id and state.

Request example:

{
  "externalClientId": "org-1042",
  "churchName": "Grace Community Church",
  "returnUrl": "https://app.example.com/cbc/connect-done",
  "prefill": {
    "contactName": "Dana Lopez",
    "contactEmail": "dana@gracecommunity.example",
    "contactPhone": "(303) 555-0142"
  }
}

Responses:

  • 201: The session was created. Send the church's browser to connectUrl within 15 minutes; the link works once.
  • 400: validation_failed (a field is missing or too long) or invalid_return_url (returnUrl isn't one of your registered connectReturnUrls; register it with PUT /settings). Nothing was created.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 409: client_already_connected: this externalClientId is already an active client. Use it (GET /clients?externalClientId=) instead of connecting again.
  • 429: rate_limited: more than 30 sessions were created in a minute (or too many invalid-key requests came from your IP address). Wait Retry-After seconds (60).

201 example (application/json):

{
  "sessionId": "8d2f6c14-3b7a-4e0e-9c55-1a9b7d3e2f60",
  "connectUrl": "https://dev.partners.cbgc.io/connect?t=Zk3qL0b2m9Xr7Tn4Vw1Yc6Hs8Pd5Ja2Ue0Gi3Ko7Mf9",
  "expiresAt": "2026-10-16T15:19:05Z"
}

400 example invalid_return_url (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_return_url",
  "title": "Return URL not registered",
  "status": 400,
  "detail": "returnUrl must exactly match one of your registered connectReturnUrls. Register it with PUT /settings first.",
  "code": "invalid_return_url"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

409 example client_already_connected (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#client_already_connected",
  "title": "Client already connected",
  "status": 409,
  "detail": "A client with externalClientId 'org-1042' is already active under your account.",
  "code": "client_already_connected"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "At most 30 connect sessions can be created per minute. Wait a minute and try again.",
  "code": "rate_limited"
}

GET /connect/sessions/{sessionId}

Get a connect session (operationId GetConnectSession)

Returns where a connect session stands: created, opened, completed (with the new clientId), cancelled, expired or migration_requested. Use it, or the client.connected webhook, to confirm a church is connected after the browser returns to you.

Parameters:

Name In Type Required Description
sessionId path string yes The session's sessionId (a UUID), from POST /connect/sessions or the session_id on the browser's return. Only your own sessions resolve.

Responses:

  • 200: Where the session stands. clientId is set once state is completed.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 404: session_not_found: no session with that id exists under your account.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

200 example (application/json):

{
  "sessionId": "8d2f6c14-3b7a-4e0e-9c55-1a9b7d3e2f60",
  "externalClientId": "org-1042",
  "state": "completed",
  "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

404 example session_not_found (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#session_not_found",
  "title": "Connect session not found",
  "status": 404,
  "detail": "No connect session '8d2f6c14-3b7a-4e0e-9c55-1a9b7d3e2f60' exists under your account.",
  "code": "session_not_found"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

Clients

Your customers (churches). Each one is a client under your account, addressed by our clientId or your own externalClientId.

GET /clients

List clients (operationId ListClients)

Returns every client under your account, oldest first. Pass externalClientId to look up one client by your own id: the list then holds that client, or nothing.

Parameters:

Name In Type Required Description
externalClientId query string no Return only the client with this externalClientId (your own id for the customer, at most 100 characters). The list then has 0 or 1 items.

Responses:

  • 200: Your clients, oldest first. With externalClientId, a list of 0 or 1 items.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

200 example (application/json):

[
  {
    "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
    "externalClientId": "org-1042",
    "name": "Grace Community Church",
    "status": "Active",
    "createdAt": "2026-10-16T15:04:05Z",
    "portalUrl": "https://portal.example.com"
  }
]

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

POST /clients

Create a client (operationId CreateClient)

Provisions one of your customers as a client under your account. Each customer needs its own client before you can order checks for them.

A portal login is created for the primary contact, and Datasource emails them their username and a temporary password (changed at first sign-in). No password is ever returned to you; welcomeEmailSent says whether the email went.

The new client starts with your account's packages and settings.

Send your own id for the customer as externalClientId and you never have to store ours: every {clientRef} path accepts it. It is unique among your clients (409 if already used).

Request body (application/json): CreateClientRequest

Field Type Required Description
externalClientId string (≤ 100 chars) | null no Your own identifier for this customer (max 100 characters, unique among your clients). Lets you address the client by your id instead of ours: use it wherever {clientRef} appears.
legalCompanyName string yes Legal registered entity name of the company or organization.
dbaCompanyName string | null no Optional 'Doing Business As' (DBA) or trade name.
primaryContactFirstName string yes First name of the primary administrator contact.
primaryContactLastName string yes Last name of the primary administrator contact.
primaryContactEmail string (email) yes Email address of the primary contact. Becomes the username of their portal login.
primaryContactPhone string | null yes Phone number of the primary contact.
companyPhone string | null no Optional main company phone number.
streetLine1 string yes Physical street address (line 1).
streetLine2 string | null no Optional physical street address (line 2, suite/unit).
city string yes Physical city.
state string yes State or province: 2-letter code (e.g. CO) or full name.
postalCode string yes Physical postal / ZIP code.
country string no Country code (US or CA). Defaults to US.
billingStreetLine1 string | null no Optional separate billing street address (line 1). If omitted, physical address is used.
billingStreetLine2 string | null no Optional separate billing street address (line 2).
billingCity string | null no Optional separate billing city.
billingState string | null no Optional separate billing state.
billingPostalCode string | null no Optional separate billing postal / ZIP code.
billingCountry string | null no Optional separate billing country code (defaults to 'US').

Request example:

{
  "externalClientId": "org-1042",
  "legalCompanyName": "Grace Community Church",
  "dbaCompanyName": "Grace Community",
  "primaryContactFirstName": "Dana",
  "primaryContactLastName": "Lopez",
  "primaryContactEmail": "dana@gracecommunity.example",
  "primaryContactPhone": "(303) 555-0142",
  "streetLine1": "1200 Larimer St",
  "streetLine2": "Suite 300",
  "city": "Denver",
  "state": "CO",
  "postalCode": "80202",
  "country": "US"
}

Responses:

  • 201: The client was created, with a portal login for its primary contact. The contact gets their username and a temporary password by email from Datasource; no password is ever returned to you. welcomeEmailSent is false if that email couldn't be sent (contact support).
  • 400: validation_failed: a required field is missing or too long. errors names each field. Nothing was created; fix the request and send it again.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 409: external_client_id_in_use: another of your clients already has this externalClientId. Nothing was created; use the existing client (GET /clients?externalClientId=).
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.
  • 502: upstream_unavailable: the screening provider didn't answer. Nothing was recorded on our side; it's safe to retry.

201 example (application/json):

{
  "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
  "externalClientId": "org-1042",
  "name": "Grace Community Church",
  "status": "ACTIVE",
  "createdAt": "2026-10-16T15:04:05Z",
  "portalUrl": "https://portal.example.com",
  "primaryUsername": "dana@gracecommunity.example",
  "welcomeEmailSent": true
}

400 example validation_failed (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#validation_failed",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "primaryContactEmail": [
      "primaryContactEmail is required."
    ]
  },
  "code": "validation_failed"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

409 example external_client_id_in_use (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#external_client_id_in_use",
  "title": "External client id in use",
  "status": 409,
  "detail": "Another of your clients already uses externalClientId 'org-1042'.",
  "code": "external_client_id_in_use"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

502 example upstream_unavailable (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#upstream_unavailable",
  "title": "Screening provider unavailable",
  "status": 502,
  "detail": "The screening provider did not accept the request. Nothing was created; it is safe to retry.",
  "code": "upstream_unavailable"
}

GET /clients/{clientRef}

Get a client (operationId GetClient)

Returns one of your clients.

Parameters:

Name In Type Required Description
clientRef path string yes The client: our clientId (cl_…) or your own externalClientId (at most 100 characters; URL-encode it). Both resolve only among your own clients; anything else is 404 client_not_found.

Responses:

  • 200: The client.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 404: client_not_found: no client with that clientId or externalClientId exists under your account. Don't retry.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

200 example (application/json):

{
  "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
  "externalClientId": "org-1042",
  "name": "Grace Community Church",
  "status": "Active",
  "createdAt": "2026-10-16T15:04:05Z",
  "portalUrl": "https://portal.example.com"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

404 example client_not_found (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#client_not_found",
  "title": "Client not found",
  "status": 404,
  "detail": "No client 'org-1042' exists under your account.",
  "code": "client_not_found"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

GET /clients/{clientRef}/settings

Get a client's settings (operationId GetClientSettings)

Returns the client's own settings and the values that apply to it (its own, else your account's defaults).

Parameters:

Name In Type Required Description
clientRef path string yes The client: our clientId (cl_…) or your own externalClientId (at most 100 characters; URL-encode it). Both resolve only among your own clients; anything else is 404 client_not_found.

Responses:

  • 200: OK
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 404: Not Found
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

404 example (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#client_not_found",
  "title": "Client not found",
  "status": 404,
  "detail": "No client 'org-1042' exists under your account.",
  "code": "client_not_found"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

PUT /clients/{clientRef}/settings

Update a client's settings (operationId UpdateClientSettings)

Sets one client's own settings. This call replaces them; send the complete set.

checkValidityYears (1 to 5) is how long this client's CLEAR or REVIEW checks stay valid: their expirationDate is completion plus that many years, and until then ordering the same applicant again needs forceReorder. Null or omitted uses your account's default (checkValidityYears in PUT /settings). A change applies to checks completed after it.

Parameters:

Name In Type Required Description
clientRef path string yes The client: our clientId (cl_…) or your own externalClientId (at most 100 characters; URL-encode it). Both resolve only among your own clients; anything else is 404 client_not_found.

Request body (application/json): ClientSettingsRequest

Field Type Required Description
checkValidityYears integer (int32; 1 to 5) | null no How many years this client's CLEAR or REVIEW checks stay valid: their expirationDate is completion plus this. A whole number from 1 to 5, or null (or omitted) to use your account's default (checkValidityYears in PUT /settings). Applies to checks completed after the change.

Responses:

  • 200: OK
  • 400: Bad Request
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 404: Not Found
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

400 example (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#validation_failed",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "level": [
      "level is required."
    ]
  },
  "code": "validation_failed"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

404 example (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#client_not_found",
  "title": "Client not found",
  "status": 404,
  "detail": "No client 'org-1042' exists under your account.",
  "code": "client_not_found"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

Products

The product levels (1 to 6) you can order, and their base prices.

GET /clients/{clientRef}/products

List a client's product levels (operationId ListClientProducts)

Returns the product levels this client can order, priced for this client (fees can differ per client). Levels the client doesn't have are left out.

Parameters:

Name In Type Required Description
clientRef path string yes The client: our clientId (cl_…) or your own externalClientId (at most 100 characters; URL-encode it). Both resolve only among your own clients; anything else is 404 client_not_found.

Responses:

  • 200: The levels this client can order, priced for it. Levels it doesn't have are left out.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 404: client_not_found: no client with that clientId or externalClientId exists under your account. Don't retry.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.
  • 502: upstream_unavailable: the screening provider didn't answer. Safe to retry.

200 example (application/json):

[
  {
    "level": 2,
    "name": "Standard",
    "description": "National criminal database, county search, SSN trace.",
    "price": 34.95,
    "feesNote": "Plus pass-through fees."
  }
]

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

404 example client_not_found (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#client_not_found",
  "title": "Client not found",
  "status": 404,
  "detail": "No client 'org-1042' exists under your account.",
  "code": "client_not_found"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

502 example upstream_unavailable (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#upstream_unavailable",
  "title": "Screening provider unavailable",
  "status": 502,
  "detail": "The screening provider did not accept the request. Nothing was created; it is safe to retry.",
  "code": "upstream_unavailable"
}

GET /products

List product levels (operationId ListProducts)

Returns every product level (1 to 6) you can order, sorted by level, with its name, description and base price. Send the level when ordering a check; you never need to know which product that maps to. Prices are base prices: third-party fees are added ("Plus pass-through fees."). Fees can differ per client, so use GET /clients/{clientRef}/products to show a client's own prices.

Responses:

  • 200: Every level, 1 to 6, with its base price from your account's products. Show feesNote next to prices.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.
  • 502: upstream_unavailable: the screening provider didn't answer. Safe to retry.

200 example (application/json):

[
  {
    "level": 2,
    "name": "Standard",
    "description": "National criminal database, county search, SSN trace.",
    "price": 34.95,
    "feesNote": "Plus pass-through fees."
  }
]

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

502 example upstream_unavailable (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#upstream_unavailable",
  "title": "Screening provider unavailable",
  "status": 502,
  "detail": "The screening provider did not accept the request. Nothing was created; it is safe to retry.",
  "code": "upstream_unavailable"
}

Checks

Order (one at a time or in batches), track, cancel and look up background checks for a client's applicants.

GET /batches/{batchId}

Get a batch (operationId GetBatch)

Returns a batch: counts of its checks by status, and one item per applicant of the original request. status is processing while any check is QUEUED or PENDING, and completed once none is.

Parameters:

Name In Type Required Description
batchId path string yes The batch's batchId (a UUID), from POST /clients/{clientRef}/checks/batch. Only your own batches resolve.

Responses:

  • 200: The batch: counts by status and one item per applicant. status is completed once no check is QUEUED or PENDING.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 404: batch_not_found: no batch with that id exists under your account.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

200 example (application/json):

{
  "batchId": "3f1c0b9e-7a52-4a6e-9d0c-5e7d2b1a8c44",
  "batchReference": "onboarding-2026-10-16",
  "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
  "status": "processing",
  "counts": {
    "QUEUED": 1,
    "PENDING": 0,
    "CREATED": 0
  },
  "items": [
    {
      "index": 0,
      "externalIdentifier": "vol-8841",
      "result": "queued",
      "check": {
        "id": "chk_9c2e4f6a1b3d4e5f8a7b6c5d",
        "externalIdentifier": "vol-8841",
        "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
        "status": "QUEUED",
        "stage": "queued",
        "level": 2,
        "expirationDate": null,
        "completedAt": null,
        "createdAt": "2026-10-16T15:10:00Z",
        "updatedAt": "2026-10-16T15:10:00Z"
      }
    },
    {
      "index": 1,
      "externalIdentifier": "vol-8842",
      "result": "invalid",
      "errors": {
        "email": [
          "email is not a valid email address."
        ]
      }
    }
  ],
  "createdAt": "2026-10-16T15:10:00Z"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

404 example batch_not_found (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#batch_not_found",
  "title": "Batch not found",
  "status": 404,
  "detail": "No batch '3f1c0b9e-7a52-4a6e-9d0c-5e7d2b1a8c44' exists under your account.",
  "code": "batch_not_found"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

GET /clients/{clientRef}/checks

List checks (operationId ListChecks)

Returns a client's checks, oldest change first, one page at a time. Use it for reconciliation, e.g. a nightly job that catches any webhook you missed.

Pass updated_since to get only checks that changed at or after that time, and follow nextCursor until it is null. A check that changes while you are paging may appear again on a later page, so process items idempotently (by id).

Parameters:

Name In Type Required Description
clientRef path string yes The client: our clientId (cl_…) or your own externalClientId (at most 100 characters; URL-encode it). Both resolve only among your own clients; anything else is 404 client_not_found.
updated_since query string (date-time) no Only return checks whose updatedAt is at or after this time. ISO 8601; a time with no offset is treated as UTC. Omit for all checks.
limit query integer (int32) no Page size, 1 to 500 (larger values are clamped). Defaults to 100.
cursor query string no The nextCursor from the previous page. Omit for the first page. An unknown value is 400 invalid_cursor.

Responses:

  • 200: One page of the client's checks, oldest change first. Follow nextCursor until it is null.
  • 400: invalid_cursor: cursor isn't a nextCursor we issued; start again from the first page. (invalid_json: updated_since or limit isn't a valid value.)
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 404: client_not_found: no client with that clientId or externalClientId exists under your account. Don't retry.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

200 example (application/json):

{
  "items": [
    {
      "id": "chk_9c2e4f6a1b3d4e5f8a7b6c5d",
      "externalIdentifier": "vol-8841",
      "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
      "status": "CLEAR",
      "stage": "complete",
      "level": 2,
      "expirationDate": "2028-10-18T18:22:41Z",
      "completedAt": "2026-10-18T18:22:41Z",
      "createdAt": "2026-10-16T15:10:00Z",
      "updatedAt": "2026-10-18T18:22:41Z"
    }
  ],
  "nextCursor": "NjM4OTYxNjU3NjE0MDAwMDAwOjEwNDI"
}

400 example invalid_cursor (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_cursor",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "cursor": [
      "cursor is not valid. Use the nextCursor value from a previous page."
    ]
  },
  "code": "invalid_cursor"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

404 example client_not_found (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#client_not_found",
  "title": "Client not found",
  "status": 404,
  "detail": "No client 'org-1042' exists under your account.",
  "code": "client_not_found"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

POST /clients/{clientRef}/checks

Order a background check (operationId OrderCheck)

Orders a background check for an applicant. The applicant receives an email invitation to provide their personal details and consent directly with us, so you only send their name, email and what to order.

What to order. Send the level (1 to 6, see GET /products). We translate the level to the client's own product; 422 Level not available means the client doesn't have that level (contact support). orderedBy is optional: it is written to the order's notes so the client's staff can see who asked, and is never stored by us or used to create a login.

Job location. Your CHMS must collect each applicant's job location and state of employment from the church and send them as jobLocation and stateOfEmployment. They decide which state's rules apply, and some levels refuse an order without them. Send only what the church tells you; never use the church's own address or another guess.

Duplicate protection. An order is rejected with 409 Conflict (and nothing is billed) when the same externalIdentifier, or the same applicant email for this client, already has a check that is in progress (PENDING, CREATED) or completed and not yet expired (CLEAR, REVIEW). Checks that ended in ERROR or CANCELLED, or have expired, can be re-ordered normally. Set forceReorder to order anyway; that is billed as a new check.

Retries. Always send an externalIdentifier, and reuse it when retrying after a timeout or network error: a retry can never create a second check.

502 responses. If submission to the screening provider fails, the check is kept as PENDING and returned in the body. Don't retry with forceReorder; the check will update via webhook or polling, or contact support with its id.

One externalIdentifier, one check, across all your clients. An externalIdentifier already used by a check of another of your clients is 409 external_identifier_in_use; nothing is ordered. Reusing it for the same client is the normal retry and re-order path.

PENDING checks resolve on their own. A check left PENDING by a lost answer is looked up with the screening provider every 10 minutes: if the provider has it, it takes the provider's status; if the provider still doesn't have it after an hour, it becomes ERROR (never submitted, so ordering again is allowed). Either way you get check.updated.

Statuses. CLEAR is reported only when the provider says no search found records. Any other completed result is REVIEW (flagged), so your users look at it in the portal.

Parameters:

Name In Type Required Description
clientRef path string yes The client: our clientId (cl_…) or your own externalClientId (at most 100 characters; URL-encode it). Both resolve only among your own clients; anything else is 404 client_not_found.

Request body (application/json): CreateOrderRequest

Field Type Required Description
externalIdentifier string (≤ 100 chars) | null no Your unique identifier for this check (max 100 characters). Strongly recommended: retries with the same value never create a second check, and it is echoed in responses and webhooks.
applicantEmail string (email) yes Applicant's email address. The invitation is sent here. Also used to detect duplicate orders for the same applicant.
applicantPhone string | null no Optional: the applicant's mobile number, 10-digit US or Canadian in any format (e.g. 801-555-1234). When sent, the invitation is also texted to it; the email is still sent. Never stored by us.
firstName string yes Applicant's first name.
lastName string yes Applicant's last name.
level integer (int32; 1 to 6) | null yes Product level to order, 1 to 6 (see GET /products).
jobLocation string (≤ 100 chars) | null no Where the applicant will work, e.g. Blue Springs, MO (at most 100 characters). Some levels require it with stateOfEmployment; send both or neither. Send it only as the church gives it.
stateOfEmployment string | null no The US state the applicant will work in (the employment jurisdiction), as a 2-letter code such as MO or a state name. Send with jobLocation.
orderedBy object | null | OrderedBy no Optional: who ordered this check. It is written to the order's notes so the client's staff can see who asked. Never stored by us and never creates a login.
forceReorder boolean no Order even if the applicant already has an in-progress or unexpired check. Defaults to false. A forced re-order is billed as a new check.

Request example:

{
  "externalIdentifier": "vol-8841",
  "firstName": "Jordan",
  "lastName": "Kim",
  "applicantEmail": "jordan.kim@example.com",
  "applicantPhone": "801-555-1234",
  "level": 2,
  "jobLocation": "Blue Springs, MO",
  "stateOfEmployment": "MO",
  "orderedBy": {
    "email": "dana@gracecommunity.example",
    "firstName": "Dana",
    "lastName": "Lopez"
  },
  "forceReorder": false
}

Responses:

  • 201: The check was submitted (status CREATED) and the applicant has been emailed. Billable. Location points at the check.
  • 400: validation_failed: a field is missing or invalid (for example no level). Nothing was ordered.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 404: client_not_found: no client with that clientId or externalClientId exists under your account. Don't retry.
  • 409: Nothing was ordered or billed. Two cases, told apart by code: duplicate_check (this body: the applicant already has a check in progress or unexpired, in order), or external_identifier_in_use (problem details: the externalIdentifier belongs to a check of another of your clients; use a different one).
  • 422: level_not_available: this client has no product for that level. A configuration problem on our side; contact support. Nothing was ordered.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.
  • 502: Two cases. If the body is a check (status PENDING, no code), the submission's answer was lost: the check may exist upstream, is kept PENDING, and is resolved automatically within about an hour (check.updated follows). Retry only with the same externalIdentifier, never with forceReorder. If the body is problem details with upstream_unavailable, nothing was created or recorded, and a retry is safe.

201 example (application/json):

{
  "id": "chk_9c2e4f6a1b3d4e5f8a7b6c5d",
  "externalIdentifier": "vol-8841",
  "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
  "status": "CREATED",
  "stage": "awaiting_applicant",
  "level": 2,
  "expirationDate": null,
  "completedAt": null,
  "createdAt": "2026-10-16T15:10:00Z",
  "updatedAt": "2026-10-16T15:10:01Z"
}

400 example validation_failed (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#validation_failed",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "level": [
      "level is required."
    ]
  },
  "code": "validation_failed"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

404 example client_not_found (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#client_not_found",
  "title": "Client not found",
  "status": 404,
  "detail": "No client 'org-1042' exists under your account.",
  "code": "client_not_found"
}

409 example duplicate_check (application/json):

{
  "code": "duplicate_check",
  "message": "A background check is currently in progress with status 'CREATED'.",
  "currentStatus": "CREATED",
  "instructions": "To force a re-order, submit the request with 'forceReorder': true.",
  "order": {
    "id": "chk_9c2e4f6a1b3d4e5f8a7b6c5d",
    "externalIdentifier": "vol-8841",
    "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
    "status": "CREATED",
    "stage": "awaiting_applicant",
    "level": 2,
    "expirationDate": null,
    "completedAt": null,
    "createdAt": "2026-10-16T15:10:00Z",
    "updatedAt": "2026-10-16T15:10:01Z"
  }
}

422 example level_not_available (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#level_not_available",
  "title": "Level not available",
  "status": 422,
  "detail": "This client has no product for level 4. Contact support.",
  "code": "level_not_available"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

502 example pending_check (application/json):

{
  "id": "chk_9c2e4f6a1b3d4e5f8a7b6c5d",
  "externalIdentifier": "vol-8841",
  "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
  "status": "PENDING",
  "stage": "submitting",
  "level": 2,
  "expirationDate": null,
  "completedAt": null,
  "createdAt": "2026-10-16T15:10:00Z",
  "updatedAt": "2026-10-16T15:10:00Z"
}

POST /clients/{clientRef}/checks/batch

Order checks for a list of people (operationId OrderBatch)

Orders checks for up to 500 people in one call. Send the level, an optional orderedBy, an optional batchReference, and the applicants.

Every applicant is validated and duplicate-checked first, against your stored checks and against the applicants before them in the same request. Each gets a result: queued, duplicate (with the existing check) or invalid (with errors). A problem with one applicant never fails the batch; only a problem with the batch itself (no applicants, more than 500, or a missing or invalid level) is a 400.

The response is 202 Accepted with a batchId. Queued checks are submitted in the background, a few at a time; each sends its own check.updated webhook as it moves from QUEUED to PENDING to CREATED, and batch.completed is sent once none is waiting. Poll GET /batches/{batchId} if you prefer.

Send a batchReference to make the call safe to retry: sending the same value again returns the first batch with 200 OK and orders nothing new. A client can have one running batch at a time (409).

An applicant whose externalIdentifier belongs to a check of another of your clients comes back as an invalid item (errors.externalIdentifier); nothing is ordered for it.

Parameters:

Name In Type Required Description
clientRef path string yes The client: our clientId (cl_…) or your own externalClientId (at most 100 characters; URL-encode it). Both resolve only among your own clients; anything else is 404 client_not_found.

Request body (application/json): BatchOrderRequest

Field Type Required Description
level integer (int32; 1 to 6) | null yes Product level to order for everyone, 1 to 6 (see GET /products).
jobLocation string (≤ 100 chars) | null no Where the applicants will work, e.g. Blue Springs, MO (at most 100 characters), for everyone without their own. Some levels require it with stateOfEmployment; send both or neither, and only as the church gives it.
stateOfEmployment string | null no The US state the applicants will work in (2-letter code or name), with jobLocation.
orderedBy object | null | OrderedBy no Optional: who ordered these checks. Written to each order's notes only; never stored and never creates a login.
batchReference string (≤ 100 chars) | null no Your idempotency key for this batch (max 100 characters). Sending the same value again returns the first batch instead of ordering again.
applicants BatchApplicant[] (≥ 1 items; ≤ 500 items) yes The people to order checks for, 1 to 500.

Request example:

{
  "level": 2,
  "batchReference": "onboarding-2026-10-16",
  "orderedBy": {
    "email": "dana@gracecommunity.example",
    "firstName": "Dana",
    "lastName": "Lopez"
  },
  "applicants": [
    {
      "firstName": "Jordan",
      "lastName": "Kim",
      "email": "jordan.kim@example.com",
      "externalIdentifier": "vol-8841"
    },
    {
      "firstName": "Sam",
      "lastName": "Ortiz",
      "email": "sam.ortiz@example",
      "externalIdentifier": "vol-8842"
    }
  ]
}

Responses:

  • 200: A batch with this batchReference already exists: this is that first batch, and nothing new was ordered.
  • 202: A new batch was accepted. Queued checks are submitted in the background; see each item's result. Billable per submitted check.
  • 400: validation_failed: a problem with the batch itself: no applicants, more than 500, a missing or invalid level, or a field too long. Problems with single applicants never fail the batch; they come back as invalid items.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 404: client_not_found: no client with that clientId or externalClientId exists under your account. Don't retry.
  • 409: batch_in_progress: this client already has a batch that is still running. Nothing was ordered. Send the batch again once GET /batches/{batchId} shows the other one completed.
  • 422: level_not_available: this client has no product for that level. Nothing was ordered; contact support.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.
  • 502: upstream_unavailable: the screening provider didn't answer. This happens before anything is stored (looking up the level's product), so nothing was ordered and a retry is safe.

200 example (application/json):

{
  "batchId": "3f1c0b9e-7a52-4a6e-9d0c-5e7d2b1a8c44",
  "batchReference": "onboarding-2026-10-16",
  "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
  "status": "processing",
  "counts": {
    "QUEUED": 1,
    "PENDING": 0,
    "CREATED": 0
  },
  "items": [
    {
      "index": 0,
      "externalIdentifier": "vol-8841",
      "result": "queued",
      "check": {
        "id": "chk_9c2e4f6a1b3d4e5f8a7b6c5d",
        "externalIdentifier": "vol-8841",
        "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
        "status": "QUEUED",
        "stage": "queued",
        "level": 2,
        "expirationDate": null,
        "completedAt": null,
        "createdAt": "2026-10-16T15:10:00Z",
        "updatedAt": "2026-10-16T15:10:00Z"
      }
    },
    {
      "index": 1,
      "externalIdentifier": "vol-8842",
      "result": "invalid",
      "errors": {
        "email": [
          "email is not a valid email address."
        ]
      }
    }
  ],
  "createdAt": "2026-10-16T15:10:00Z"
}

202 example (application/json):

{
  "batchId": "3f1c0b9e-7a52-4a6e-9d0c-5e7d2b1a8c44",
  "batchReference": "onboarding-2026-10-16",
  "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
  "status": "processing",
  "counts": {
    "QUEUED": 1,
    "PENDING": 0,
    "CREATED": 0
  },
  "items": [
    {
      "index": 0,
      "externalIdentifier": "vol-8841",
      "result": "queued",
      "check": {
        "id": "chk_9c2e4f6a1b3d4e5f8a7b6c5d",
        "externalIdentifier": "vol-8841",
        "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
        "status": "QUEUED",
        "stage": "queued",
        "level": 2,
        "expirationDate": null,
        "completedAt": null,
        "createdAt": "2026-10-16T15:10:00Z",
        "updatedAt": "2026-10-16T15:10:00Z"
      }
    },
    {
      "index": 1,
      "externalIdentifier": "vol-8842",
      "result": "invalid",
      "errors": {
        "email": [
          "email is not a valid email address."
        ]
      }
    }
  ],
  "createdAt": "2026-10-16T15:10:00Z"
}

400 example validation_failed (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#validation_failed",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "applicants": [
      "applicants can hold at most 500 applicants per batch."
    ]
  },
  "code": "validation_failed"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

404 example client_not_found (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#client_not_found",
  "title": "Client not found",
  "status": 404,
  "detail": "No client 'org-1042' exists under your account.",
  "code": "client_not_found"
}

409 example batch_in_progress (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#batch_in_progress",
  "title": "Batch in progress",
  "status": 409,
  "detail": "This client already has a batch that is still running. Wait for it to complete (GET /batches/{batchId}) before sending another.",
  "code": "batch_in_progress"
}

422 example level_not_available (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#level_not_available",
  "title": "Level not available",
  "status": 422,
  "detail": "This client has no product for level 4. Contact support.",
  "code": "level_not_available"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

502 example upstream_unavailable (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#upstream_unavailable",
  "title": "Screening provider unavailable",
  "status": 502,
  "detail": "The screening provider did not accept the request. Nothing was created; it is safe to retry.",
  "code": "upstream_unavailable"
}

POST /clients/{clientRef}/checks/lookup

Find existing checks by email (operationId LookupChecks)

Finds the latest check for each of up to 500 applicant emails, including history imported when a church was moved to you. Each result is keyed by the email's position in your request (index): emails are never returned. Use it to match your own people to existing checks, then link them with PATCH /clients/{clientRef}/checks/{checkRef}.

Parameters:

Name In Type Required Description
clientRef path string yes The client: our clientId (cl_…) or your own externalClientId (at most 100 characters; URL-encode it). Both resolve only among your own clients; anything else is 404 client_not_found.

Request body (application/json): CheckLookupRequest

Field Type Required Description
emails string (email)[] (≥ 1 items; ≤ 500 items) yes 1 to 500 applicant email addresses. Matching ignores case and surrounding spaces. The response never contains an email.

Request example:

{
  "emails": [
    "jordan.kim@example.com",
    "sam.ortiz@example.com"
  ]
}

Responses:

  • 200: One item per email, in request order, keyed by index. check is the latest check for that email at this client, or null. Emails are never returned.
  • 400: validation_failed: emails is empty, has more than 500 entries, or an entry is longer than 254 characters.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 404: client_not_found: no client with that clientId or externalClientId exists under your account. Don't retry.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

200 example (application/json):

{
  "items": [
    {
      "index": 0,
      "check": {
        "id": "chk_9c2e4f6a1b3d4e5f8a7b6c5d",
        "externalIdentifier": null,
        "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
        "status": "CLEAR",
        "stage": "complete",
        "level": null,
        "expirationDate": "2028-01-07T18:22:41Z",
        "completedAt": "2026-01-07T18:22:41Z",
        "createdAt": "2026-01-05T15:10:00Z",
        "updatedAt": "2026-01-07T18:22:41Z"
      }
    },
    {
      "index": 1,
      "check": null
    }
  ]
}

400 example validation_failed (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#validation_failed",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "emails": [
      "emails can hold at most 500 addresses."
    ]
  },
  "code": "validation_failed"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

404 example client_not_found (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#client_not_found",
  "title": "Client not found",
  "status": 404,
  "detail": "No client 'org-1042' exists under your account.",
  "code": "client_not_found"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

GET /clients/{clientRef}/checks/{checkRef}

Get a check (operationId GetCheck)

Returns one check. checkRef may be the check's id (chk_…) or your externalIdentifier.

Parameters:

Name In Type Required Description
clientRef path string yes The client: our clientId (cl_…) or your own externalClientId (at most 100 characters; URL-encode it). Both resolve only among your own clients; anything else is 404 client_not_found.
checkRef path string yes The check: its id (chk_…) or your externalIdentifier. Resolves only among your own checks (of that client, where the path names one).

Responses:

  • 200: The check's current state.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 404: client_not_found or check_not_found: the client, or the check within it, doesn't exist under your account. Don't retry.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

200 example (application/json):

{
  "id": "chk_9c2e4f6a1b3d4e5f8a7b6c5d",
  "externalIdentifier": "vol-8841",
  "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
  "status": "CREATED",
  "stage": "awaiting_applicant",
  "level": 2,
  "expirationDate": null,
  "completedAt": null,
  "createdAt": "2026-10-16T15:10:00Z",
  "updatedAt": "2026-10-16T15:10:01Z"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

404 example check_not_found (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#check_not_found",
  "title": "Check not found",
  "status": 404,
  "detail": "No check 'vol-8841' exists for client 'org-1042'.",
  "code": "check_not_found"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

PATCH /clients/{clientRef}/checks/{checkRef}

Set your identifier on a check (operationId UpdateCheck)

Sets externalIdentifier on a check that doesn't have one yet, so you can link a check (for example one found with POST /clients/{clientRef}/checks/lookup) to your own record. 409 if the check already has an identifier, or if another of your checks uses that value. checkRef is the check's id (chk_…).

Parameters:

Name In Type Required Description
clientRef path string yes The client: our clientId (cl_…) or your own externalClientId (at most 100 characters; URL-encode it). Both resolve only among your own clients; anything else is 404 client_not_found.
checkRef path string yes The check: its id (chk_…) or your externalIdentifier. Resolves only among your own checks (of that client, where the path names one).

Request body (application/json): UpdateCheckRequest

Field Type Required Description
externalIdentifier string (≤ 100 chars) | null yes Your identifier for the check (max 100 characters). Allowed only while the check has none, and it must be unused by your other checks.

Request example:

{
  "externalIdentifier": "vol-8841"
}

Responses:

  • 200: The check, now with your externalIdentifier.
  • 400: validation_failed: externalIdentifier is missing or longer than 100 characters.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 404: client_not_found or check_not_found: the client, or the check within it, doesn't exist under your account. Don't retry.
  • 409: external_identifier_in_use: the check already has an externalIdentifier (it can be set once), or another of your checks uses that value. Nothing changed.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

200 example (application/json):

{
  "id": "chk_9c2e4f6a1b3d4e5f8a7b6c5d",
  "externalIdentifier": "vol-8841",
  "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
  "status": "CREATED",
  "stage": "awaiting_applicant",
  "level": 2,
  "expirationDate": null,
  "completedAt": null,
  "createdAt": "2026-10-16T15:10:00Z",
  "updatedAt": "2026-10-16T15:10:01Z"
}

400 example validation_failed (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#validation_failed",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "externalIdentifier": [
      "externalIdentifier is required."
    ]
  },
  "code": "validation_failed"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

404 example check_not_found (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#check_not_found",
  "title": "Check not found",
  "status": 404,
  "detail": "No check 'vol-8841' exists for client 'org-1042'.",
  "code": "check_not_found"
}

409 example external_identifier_in_use (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#external_identifier_in_use",
  "title": "External identifier in use",
  "status": 409,
  "detail": "Another of your checks already uses that externalIdentifier.",
  "code": "external_identifier_in_use"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

POST /clients/{clientRef}/checks/{checkRef}/cancel

Cancel a check (operationId CancelCheck)

Cancels a check, e.g. one ordered by mistake. Cancel before the applicant submits their information to avoid charges. checkRef may be the check's id (chk_…) or your externalIdentifier.

The check is marked CANCELLED either way. upstreamCancelled tells you whether the screening provider confirmed it; if it's false, the check may still be processed, so contact support.

Parameters:

Name In Type Required Description
clientRef path string yes The client: our clientId (cl_…) or your own externalClientId (at most 100 characters; URL-encode it). Both resolve only among your own clients; anything else is 404 client_not_found.
checkRef path string yes The check: its id (chk_…) or your externalIdentifier. Resolves only among your own checks (of that client, where the path names one).

Responses:

  • 200: The check is now CANCELLED. If upstreamCancelled is false the screening provider didn't confirm it and the check may still be processed and billed: contact support.
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 404: client_not_found or check_not_found: the client, or the check within it, doesn't exist under your account. Don't retry.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

200 example (application/json):

{
  "id": "chk_9c2e4f6a1b3d4e5f8a7b6c5d",
  "externalIdentifier": "vol-8841",
  "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
  "status": "CANCELLED",
  "stage": "closed",
  "level": 2,
  "expirationDate": null,
  "completedAt": "2026-10-16T16:00:00Z",
  "createdAt": "2026-10-16T15:10:00Z",
  "updatedAt": "2026-10-16T16:00:00Z",
  "upstreamCancelled": true
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

404 example check_not_found (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#check_not_found",
  "title": "Check not found",
  "status": 404,
  "detail": "No check 'vol-8841' exists for client 'org-1042'.",
  "code": "check_not_found"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

POST /clients/{clientRef}/checks/{checkRef}/remind

Re-send the applicant's invitation (operationId RemindApplicant)

Sends the applicant their invitation again: by email, and by text if applicantPhone was sent with the order. Only for a check waiting for the applicant (CREATED, stage awaiting_applicant), at most once an hour. checkRef may be the check's id (chk_…) or your externalIdentifier.

409 check_not_awaiting_applicant if the applicant already finished (or the check is closed); 409 reminder_too_soon within an hour of the last reminder.

Parameters:

Name In Type Required Description
clientRef path string yes The client: our clientId (cl_…) or your own externalClientId (at most 100 characters; URL-encode it). Both resolve only among your own clients; anything else is 404 client_not_found.
checkRef path string yes The check: its id (chk_…) or your externalIdentifier. Resolves only among your own checks (of that client, where the path names one).

Responses:

  • 200: OK
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 404: Not Found
  • 409: Conflict
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.
  • 502: Bad Gateway

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

404 example (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#client_not_found",
  "title": "Client not found",
  "status": 404,
  "detail": "No client 'org-1042' exists under your account.",
  "code": "client_not_found"
}

409 example (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#client_not_found",
  "title": "Client not found",
  "status": 404,
  "detail": "No client 'org-1042' exists under your account.",
  "code": "client_not_found"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

502 example (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#client_not_found",
  "title": "Client not found",
  "status": 404,
  "detail": "No client 'org-1042' exists under your account.",
  "code": "client_not_found"
}

POST /clients/{clientRef}/checks/{checkRef}/replay-webhook

Re-send a check's webhook (operationId ReplayWebhook)

Queues a new delivery of the check.updated webhook with the check's current state, e.g. after your endpoint was down. Delivery is asynchronous and retried like any other webhook. checkRef may be the check's id (chk_…) or your externalIdentifier.

Parameters:

Name In Type Required Description
clientRef path string yes The client: our clientId (cl_…) or your own externalClientId (at most 100 characters; URL-encode it). Both resolve only among your own clients; anything else is 404 client_not_found.
checkRef path string yes The check: its id (chk_…) or your externalIdentifier. Resolves only among your own checks (of that client, where the path names one).

Responses:

  • 200: A new check.updated delivery with the check's current state was queued (a new eventId).
  • 401: invalid_api_key: the API key is missing, invalid or revoked, or it's a test key on production (or the reverse). Don't retry until the key is fixed.
  • 404: client_not_found or check_not_found: the client, or the check within it, doesn't exist under your account. Don't retry.
  • 429: rate_limited: too many requests with an invalid API key from your IP address. Wait the number of seconds in Retry-After. Requests with a valid key are never throttled this way.

200 example (application/json):

{
  "message": "Webhook replay request accepted",
  "id": "chk_9c2e4f6a1b3d4e5f8a7b6c5d",
  "status": "QUEUED"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#invalid_api_key",
  "title": "Invalid API key",
  "status": 401,
  "detail": "The API key is missing, invalid or revoked.",
  "code": "invalid_api_key"
}

404 example check_not_found (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#check_not_found",
  "title": "Check not found",
  "status": 404,
  "detail": "No check 'vol-8841' exists for client 'org-1042'.",
  "code": "check_not_found"
}

429 example rate_limited (application/problem+json):

{
  "type": "https://partners.cbgc.io/docs/errors.md#rate_limited",
  "title": "Too many requests",
  "status": 429,
  "detail": "Too many requests with an invalid API key from your IP address. Wait for the number of seconds in the Retry-After header.",
  "code": "rate_limited"
}

Webhooks

Every webhook is a POST of a flat JSON object with event, eventId and timestamp, signed in X-Partner-Signature (lowercase hex HMAC-SHA256 of the raw body, keyed with your whole whsec_... secret). Respond 2xx within 15 seconds. Delivery is at least once: drop repeats by eventId.

Webhook check.updated: Check status changed

Sent to your webhook URL whenever a check changes status (including each step of a batch and automatic resolution of a PENDING check), and when you call Re-send a check's webhook. The payload carries both status and the finer stage.

Verify every delivery before trusting it: compute HMAC-SHA256 over the raw request body (the exact bytes you received, before parsing), keyed with the UTF-8 bytes of your whole webhook secret (whsec_ prefix included), hex-encode it in lowercase, and compare it to the X-Partner-Signature header in constant time.

// Node.js 20+ (rawBody is a Buffer of the exact request body)
import crypto from "node:crypto";
const expected = crypto.createHmac("sha256", process.env.DATASOURCE_WEBHOOK_SECRET).update(rawBody).digest("hex");
const given = Buffer.from(req.get("X-Partner-Signature") ?? "", "utf8");
const valid = given.length === expected.length && crypto.timingSafeEqual(given, Buffer.from(expected, "utf8"));
# Python 3
import hashlib, hmac, os
expected = hmac.new(os.environ["DATASOURCE_WEBHOOK_SECRET"].encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
valid = hmac.compare_digest(expected, request.headers.get("X-Partner-Signature", ""))
// C#
var expected = Convert.ToHexString(HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), rawBody)).ToLowerInvariant();
var valid = CryptographicOperations.FixedTimeEquals(Encoding.UTF8.GetBytes(expected), Encoding.UTF8.GetBytes(signatureHeader ?? ""));

Delivery. Respond with any 2xx within 15 seconds; do slow work asynchronously. Anything else is retried up to 5 more times with increasing delays. Delivery is at least once and not ordered: drop repeats by eventId (the same on every retry of one event), and keep the state with the latest timestamp for each check (key checks by id).

Field Type Required Description
event string (one of "check.updated") no Event type. Currently always check.updated.
eventId string (uuid) no Unique id of this event, the same on every retry of its delivery. Use it to drop duplicate deliveries.
id string no Identifier of the check (chk_…, same as id in API responses).
externalIdentifier string | null no Your identifier for the check, as sent when ordering. The easiest way to match the webhook to your records.
clientId string no Client the check belongs to (its clientId).
status string (one of "QUEUED", "PENDING", "CREATED", "CLEAR", "REVIEW", "ERROR", "CANCELLED") no QUEUED: in a batch, not yet submitted. PENDING: accepted, being submitted. CREATED: submitted; the applicant has been invited to complete their information. CLEAR: completed, no search found records. REVIEW: completed and flagged (records found, or the result needs a person to look). ERROR: could not be completed. CANCELLED: cancelled. CLEAR, REVIEW, ERROR and CANCELLED are final.
stage string (one of "queued", "submitting", "awaiting_applicant", "applicant_ready", "in_progress", "complete", "closed") no Where the check is, finer than status: queued (in a batch, not yet sent), submitting (being sent), awaiting_applicant (invite sent; the applicant hasn't submitted), applicant_ready (the applicant submitted, and the church must review and release the order in its portal, e.g. a possible duplicate or a jurisdiction question), in_progress (the applicant submitted; searches are running), complete (final result ready: CLEAR or REVIEW), closed (will not complete: ERROR or CANCELLED). awaiting_applicant, applicant_ready and in_progress are all CREATED; each move between them sends check.updated.
level integer (int32; 1 to 6) | null no Product level the check was ordered with (1 to 6), or null for a check imported from the church's history.
expirationDate string (date-time) | null no When a completed check expires (UTC).
completedAt string (date-time) | null no When the check reached a final status (UTC).
reportUrl string | null no Only for CLEAR and REVIEW: the report in the church's Datasource portal (needs the church's sign-in). Null otherwise.
charges number (double) | null no Only for a final status: what the screening provider charged, in US dollars. Null otherwise.
errorCode string | null no Only for ERROR: why, as a stable code (see the check's errorCode). Null otherwise.
errorMessage string | null no Only for ERROR: one sentence for the church's staff saying what went wrong and what to do. Null otherwise.
timestamp string (date-time) no When this webhook was generated (UTC). Deliveries can arrive out of order; use the latest timestamp per check.

Example:

{
  "event": "check.updated",
  "eventId": "e7a1c3b5-2d4f-4a6b-8c9d-0e1f2a3b4c5d",
  "id": "chk_9c2e4f6a1b3d4e5f8a7b6c5d",
  "externalIdentifier": "vol-8841",
  "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
  "status": "CLEAR",
  "stage": "complete",
  "level": 2,
  "expirationDate": "2028-10-18T18:22:41Z",
  "completedAt": "2026-10-18T18:22:41Z",
  "timestamp": "2026-10-18T18:22:42Z"
}

Webhook batch.completed: Batch completed

Sent once when no check of a batch is QUEUED or PENDING any more: every check has been submitted (or released). It is not a results event; results come as check.updated. Same signature, retry and eventId rules as check.updated.

Field Type Required Description
event string (one of "batch.completed") no Always batch.completed.
eventId string (uuid) no Unique id of this event, the same on every retry of its delivery. Use it to drop duplicate deliveries.
timestamp string (date-time) no When this webhook was generated (UTC).
batchId string (uuid) no The batch, as returned by POST /clients/{clientRef}/checks/batch.
batchReference string | null no Your batchReference for the batch, if you sent one.
clientId string no The client the batch was ordered for (its clientId).
externalClientId string | null no Your own id for the client, if it has one.
counts object no Number of checks per status at completion, e.g. { "CREATED": 12 }.

Example:

{
  "event": "batch.completed",
  "eventId": "0b8f2e6d-4c1a-4f3e-9d7b-5a2c8e1f6d30",
  "timestamp": "2026-10-16T15:12:30Z",
  "batchId": "3f1c0b9e-7a52-4a6e-9d0c-5e7d2b1a8c44",
  "batchReference": "onboarding-2026-10-16",
  "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
  "externalClientId": "org-1042",
  "counts": {
    "QUEUED": 0,
    "PENDING": 0,
    "CREATED": 1
  }
}

Webhook client.connected: Church connected

Sent when a church finishes the hosted connect page and its client exists (status is Active). Confirm a connection with this event or GET /connect/sessions/{sessionId} before treating the church as connected; never with the browser redirect alone. Same signature, retry and eventId rules as check.updated.

Field Type Required Description
event string (one of "client.connected") no Always client.connected.
eventId string (uuid) no Unique id of this event, the same on every retry of its delivery. Use it to drop duplicate deliveries.
timestamp string (date-time) no When this webhook was generated (UTC).
sessionId string (uuid) no The connect session that created the client.
externalClientId string no The externalClientId you sent when creating the session.
clientId string no The new client's clientId.
status string (one of "Active") no The client's status. Always Active for a newly connected church.

Example:

{
  "event": "client.connected",
  "eventId": "6c3d9a1e-8b2f-4e5d-a7c0-1f9e3b5d7a24",
  "timestamp": "2026-10-16T15:16:40Z",
  "sessionId": "8d2f6c14-3b7a-4e0e-9c55-1a9b7d3e2f60",
  "externalClientId": "org-1042",
  "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
  "status": "Active"
}

Webhook client.migrated: Church migrated

Sent when an existing Datasource church has been moved under your account and its past checks were imported (importedChecks). Same signature, retry and eventId rules as check.updated.

Field Type Required Description
event string (one of "client.migrated") no Always client.migrated.
eventId string (uuid) no Unique id of this event, the same on every retry of its delivery. Use it to drop duplicate deliveries.
timestamp string (date-time) no When this webhook was generated (UTC).
externalClientId string no The externalClientId you sent when creating the connect session.
clientId string no The moved client's clientId.
importedChecks integer (int32) no How many past checks were imported for the client.

Example:

{
  "event": "client.migrated",
  "eventId": "a4e2c8f0-3d6b-4a1e-b9c5-7d0f2e4a6c81",
  "timestamp": "2026-10-20T14:02:11Z",
  "externalClientId": "org-1042",
  "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
  "importedChecks": 214
}

Webhook check.expiring: Check expiring soon

Sent 30 days and again 7 days before a CLEAR or REVIEW check's expirationDate (each once; a check found later than 30 days out gets only the 7-day one). Use it to prompt the church to re-screen before the clearance lapses. Same signature, retry and eventId rules as check.updated.

Field Type Required Description
event string no Always check.expiring.
eventId string no Unique id of this event, the same on every retry of its delivery. Use it to drop duplicate deliveries.
timestamp string (date-time) no When this webhook was generated (UTC).
id string no The check's id (chk_…).
externalIdentifier string | null no Your identifier for the check, as sent when ordering.
clientId string no The client (church) the check belongs to (cl_…).
status string no CLEAR or REVIEW.
expirationDate string (date-time) no When the check expires (UTC).
daysLeft integer (int32) no Whole days until expirationDate (30 or fewer).

Example:

{
  "event": "check.expiring",
  "eventId": "a4c6e8f0-2b4d-4f6a-8c0e-1a3c5e7f9b2d",
  "timestamp": "2028-09-18T14:00:03Z",
  "id": "chk_9c2e4f6a1b3d4e5f8a7b6c5d",
  "externalIdentifier": "vol-8841",
  "clientId": "cl_5b0e6f2a8c1d4e7b9a3f2d6c",
  "status": "CLEAR",
  "expirationDate": "2028-10-18T18:22:41Z",
  "daysLeft": 30
}

Webhook ping: Test ping

Sent by POST /settings/webhook-test so you can verify your endpoint and your signature check. Same signature, retry and eventId rules as check.updated.

Field Type Required Description
event string (one of "ping") no Always ping.
eventId string (uuid) no Unique id of this event, the same on every retry of its delivery. Use it to drop duplicate deliveries.
timestamp string (date-time) no When this webhook was generated (UTC).

Example:

{
  "event": "ping",
  "eventId": "f1d3b5a7-9c2e-4d6f-8a0b-3c5e7f9a1b2d",
  "timestamp": "2026-10-16T15:00:00Z"
}