Skip to content
Datasource Wholesale Background Check API

API reference: Datasource Wholesale Background Check 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://api.datasourcecorp.app/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://api.datasourcecorp.app/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, your webhook signing secret, and your default check validity.

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://api.datasourcecorp.app/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://api.datasourcecorp.app/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.

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). This call replaces your settings; send the complete set. (connectReturnUrls isn't used by wholesale accounts.)

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 Not used by wholesale accounts.
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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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 own account (account) and any sub-accounts you create to keep your customers' checks apart. Every check is billed to your account.

GET /clients

List clients (operationId ListClients)

Returns your account (account, the first item) and every sub-account, oldest first. Pass externalClientId to look up one sub-account by your own id: the list then holds that sub-account, 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 account first, then your sub-accounts, 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_2e4c6a8b0d1f3e5a7c9b1d3f",
    "externalClientId": "acct-7731",
    "name": "Northwind Staffing - Denver",
    "status": "Active",
    "createdAt": "2026-11-14T15:04:05Z",
    "portalUrl": "https://portal.example.com"
  }
]

401 example invalid_api_key (application/problem+json):

{
  "type": "https://api.datasourcecorp.app/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://api.datasourcecorp.app/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)

Creates a sub-account under your account. You don't need one to order: order on account. Use sub-accounts to keep one of your customers' checks, reports and portal users apart from the rest. Checks ordered on a sub-account are billed to your account, at your prices.

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

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": "acct-7731",
  "legalCompanyName": "Northwind Staffing LLC",
  "dbaCompanyName": "Northwind Staffing - Denver",
  "primaryContactFirstName": "Riley",
  "primaryContactLastName": "Chen",
  "primaryContactEmail": "riley.chen@northwind.example",
  "primaryContactPhone": "(303) 555-0142",
  "streetLine1": "1600 Champa St",
  "streetLine2": "Suite 500",
  "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_2e4c6a8b0d1f3e5a7c9b1d3f",
  "externalClientId": "acct-7731",
  "name": "Northwind Staffing - Denver",
  "status": "ACTIVE",
  "createdAt": "2026-11-14T15:04:05Z",
  "portalUrl": "https://portal.example.com",
  "primaryUsername": "riley.chen@northwind.example",
  "welcomeEmailSent": true
}

400 example validation_failed (application/problem+json):

{
  "type": "https://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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 Where the check is ordered: account (your own account), or a sub-account's clientId (cl_…) or your externalClientId for it. Every check is billed to your account. 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_2e4c6a8b0d1f3e5a7c9b1d3f",
  "externalClientId": "acct-7731",
  "name": "Northwind Staffing - Denver",
  "status": "Active",
  "createdAt": "2026-11-14T15:04:05Z",
  "portalUrl": "https://portal.example.com"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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 Where the check is ordered: account (your own account), or a sub-account's clientId (cl_…) or your externalClientId for it. Every check is billed to your account. 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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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 Where the check is ordered: account (your own account), or a sub-account's clientId (cl_…) or your externalClientId for it. Every check is billed to your account. 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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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, at your wholesale 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 Where the check is ordered: account (your own account), or a sub-account's clientId (cl_…) or your externalClientId for it. Every check is billed to your account. 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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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 checks with the applicant's full details, then track, cancel and reconcile them.

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 Where the check is ordered: account (your own account), or a sub-account's clientId (cl_…) or your externalClientId for it. Every check is billed to your account. 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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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/direct

Order a background check (operationId OrderDirectCheck)

Orders a background check with the applicant's full details. Nothing is sent to the applicant: you collect their details, give them the disclosure and get their written authorization before you order, and certify that you did in certification. The details go to the screening provider for this order only; we never store or log them (the SSN is kept only as a keyed one-way hash, to catch duplicate orders).

Where it's billed. Order on your own account with {clientRef} account: every check is billed to you at your wholesale prices. If you created sub-accounts (POST /clients) to keep your customers' checks apart, order on one with its clientId or your externalClientId; those checks are billed to you as well.

What to order. Send the level (1 to 6, see GET /products). 422 level_not_available means the account doesn't have that level (contact support). Levels that include a driving record need applicant.driversLicense.

Job location. Some levels need jobLocation and stateOfEmployment (where the applicant will work; send both or neither). They decide which state's rules apply.

Duplicate protection. An order is rejected with 409 Conflict (and nothing is billed) when the same externalIdentifier, or the same applicant (by SSN) on this account, already has a check that is in progress (PENDING, CREATED) or completed and not yet expired (CLEAR, REVIEW). 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; it resolves on its own (check.updated follows). Don't retry with forceReorder.

Parameters:

Name In Type Required Description
clientRef path string yes Where the check is ordered: account (your own account), or a sub-account's clientId (cl_…) or your externalClientId for it. Every check is billed to your account. Anything else is 404 client_not_found.

Request body (application/json): DirectOrderRequest

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.
level integer (int32; 1 to 6) | null yes Product level to order, 1 to 6 (see GET /products).
applicant object | null | DirectApplicant yes The applicant's identifying details. Used for this order only; never stored or logged by us.
certification object | null | OrderCertification yes Your certification for this order. Every order needs it; an order without a complete certification is rejected and nothing is billed.
jobLocation string (≤ 100 chars) | null no Where the applicant will work, e.g. Denver, CO (at most 100 characters). Some levels require it with stateOfEmployment; send both or neither.
stateOfEmployment string | null no The US state the applicant will work in (the employment jurisdiction), as a 2-letter code such as CO. Send with jobLocation.
orderedBy object | null | OrderedBy no Optional: who ordered this check. It is written to the order's notes so your staff can see who asked. Never stored by us and never creates a login.
forceReorder boolean no Order even if this applicant (matched by SSN) already has an in-progress or unexpired check. Defaults to false. A forced re-order is billed as a new check.

Request example:

{
  "externalIdentifier": "app-20931",
  "level": 2,
  "applicant": {
    "firstName": "Jordan",
    "middleName": "Lee",
    "lastName": "Kim",
    "dateOfBirth": "1990-04-12",
    "ssn": "078-05-1120",
    "email": "jordan.kim@example.com",
    "phone": "303-555-0187",
    "address": {
      "street": "1200 Larimer St",
      "street2": "Apt 4",
      "city": "Denver",
      "state": "CO",
      "postalCode": "80202"
    }
  },
  "certification": {
    "permissiblePurpose": "employment",
    "disclosureProvided": true,
    "authorizationObtained": true
  },
  "jobLocation": "Denver, CO",
  "stateOfEmployment": "CO",
  "orderedBy": {
    "email": "hr@northwind.example",
    "firstName": "Riley",
    "lastName": "Chen"
  },
  "forceReorder": false
}

Responses:

  • 201: The check was submitted (status CREATED). Billable. Location points at the check. Nothing was sent to the applicant.
  • 400: validation_failed: a field is missing or invalid, or the certification is incomplete. errors names each field; it never repeats the values you sent. 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.
  • 403: not_available_for_account_type: this key belongs to an invitation-based (CHMS) account, which orders with POST /clients/{clientRef}/checks.
  • 404: client_not_found: {clientRef} isn't account or one of your sub-accounts. Don't retry.
  • 409: Nothing was ordered or billed. Two cases, told apart by code: duplicate_check (this body: this applicant, matched by SSN, already has a check in progress or unexpired on this account, in order), or external_identifier_in_use (problem details: the externalIdentifier belongs to a check on another of your accounts; use a different one).
  • 422: level_not_available: the account has no product for that level. 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 resolves on its own 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, 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://api.datasourcecorp.app/docs/errors.md#validation_failed",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "applicant.ssn": [
      "applicant.ssn must be a valid 9-digit Social Security number."
    ]
  },
  "code": "validation_failed"
}

401 example invalid_api_key (application/problem+json):

{
  "type": "https://api.datasourcecorp.app/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"
}

403 example not_available_for_account_type (application/problem+json):

{
  "type": "https://api.datasourcecorp.app/docs/errors.md#not_available_for_account_type",
  "title": "Not available for your account type",
  "status": 403,
  "detail": "This operation is for wholesale accounts.",
  "code": "not_available_for_account_type"
}

404 example client_not_found (application/problem+json):

{
  "type": "https://api.datasourcecorp.app/docs/errors.md#client_not_found",
  "title": "Client not found",
  "status": 404,
  "detail": "No client 'acct-7731' 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": "app-20931",
    "clientId": "cl_7d1a3c5e9b2f4a6c8e0d1b3f",
    "status": "CREATED",
    "stage": "in_progress",
    "level": 2,
    "expirationDate": null,
    "completedAt": null,
    "createdAt": "2026-11-14T15:10:00Z",
    "updatedAt": "2026-11-14T15:10:01Z"
  }
}

422 example level_not_available (application/problem+json):

{
  "type": "https://api.datasourcecorp.app/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://api.datasourcecorp.app/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": "app-20931",
  "clientId": "cl_7d1a3c5e9b2f4a6c8e0d1b3f",
  "status": "PENDING",
  "stage": "submitting",
  "level": 2,
  "expirationDate": null,
  "completedAt": null,
  "createdAt": "2026-11-14T15:10:00Z",
  "updatedAt": "2026-11-14T15:10:00Z"
}

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 Where the check is ordered: account (your own account), or a sub-account's clientId (cl_…) or your externalClientId for it. Every check is billed to your account. 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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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 Where the check is ordered: account (your own account), or a sub-account's clientId (cl_…) or your externalClientId for it. Every check is billed to your account. 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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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 Where the check is ordered: account (your own account), or a sub-account's clientId (cl_…) or your externalClientId for it. Every check is billed to your account. 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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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}/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 Where the check is ordered: account (your own account), or a sub-account's clientId (cl_…) or your externalClientId for it. Every check is billed to your account. 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://api.datasourcecorp.app/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://api.datasourcecorp.app/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://api.datasourcecorp.app/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), applicant_ready (held: someone must review and release the order in the Datasource portal, e.g. a possible duplicate or a jurisdiction question), in_progress (searches are running), complete (final result ready: CLEAR or REVIEW), closed (will not complete: ERROR or CANCELLED). applicant_ready and in_progress are both 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 account'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 Datasource portal (needs a portal 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 your 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 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 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 account or sub-account 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"
}