Skip to content
Datasource Wholesale Background Check API

Datasource Wholesale Background Check API: Integration Guide

Order background checks from your own platform with the applicant's full details, billed to your account at wholesale prices. You run the applicant-facing flow (collecting their details, the disclosure and their authorization); we run the check and tell you, with signed webhooks, as it moves.

This guide covers everything end to end. The interactive reference is at /scalar, the OpenAPI 3.1 document at /openapi/v1.json, and the error catalog lists every error code.

1. Start here

Environment Base URL API keys Checks
Test (sandbox) https://sandbox.datasourcecorp.app pk_test_... Not real; nothing is charged
Production https://api.datasourcecorp.app pk_live_... Real and billed
  1. GET /status with your test key. 200 means the key works.
  2. PUT /settings with your webhook URL, then POST /settings/webhook-test to check that your endpoint verifies our signature.
  3. GET /products: the levels (1 to 6) you can order, at your prices.
  4. POST /clients/account/checks/direct with the applicant's details, a level and your certification.
  5. Handle check.updated webhooks, and reconcile nightly with GET /clients/account/checks?updated_since=....
curl https://sandbox.datasourcecorp.app/status -H "X-Api-Key: pk_test_YOUR_KEY"

2. Concepts

  • Account. Your own account. In any path, {clientRef} account means it. Every check is billed to it, at your wholesale prices.
  • Sub-account (optional). POST /clients creates one under your account, to keep one of your customers' checks, reports and portal users apart from the rest. Address it by our clientId (cl_…) or your own externalClientId. Checks ordered on a sub-account are still billed to your account. You never need one to order.
  • Check. One background check for one applicant. It has a contract-stable status and a finer stage. Send your own externalIdentifier (unique across your account and sub-accounts) and key your records by our id (chk_…).
  • Level. What to order: levels 1 to 6 (GET /products). We translate the level to the product on the account.
  • Certification. On every order you certify the permissible purpose, that you gave the applicant the disclosure, and that you have their written authorization (section 4).

3. Authentication

Send your key on every request as X-Api-Key: <key> or Authorization: Bearer <key>. Keys are issued by Datasource and shown once: keep them in a secret manager and use them only from your servers, never in a browser or mobile app. You can have two active keys at once, so you can rotate without downtime (ask support for the second key, deploy it, then ask us to revoke the first).

A missing, wrong or revoked key is 401 invalid_api_key. A pk_test_ key doesn't work on production, and the reverse. Repeated invalid keys from one IP address get 429 rate_limited for a minute; a valid key is never throttled that way.

4. Ordering a check

curl https://sandbox.datasourcecorp.app/clients/account/checks/direct \
  -H "X-Api-Key: pk_test_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{
    "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",
      "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"
  }'

201 Created returns the check (status CREATED) and a Location header. The check is billable from here.

The applicant

Field Required Notes
firstName, lastName yes Legal name, at most 100 characters each.
middleName when there is one Omit if the applicant has none. Some searches match on it.
dateOfBirth yes YYYY-MM-DD. The applicant must be 14 to 120 years old.
ssn yes 9 digits, with or without dashes. Numbers the SSA never issues (area 000, 666 or 9xx, group 00, serial 0000) are rejected.
address yes The current home address: street, optional street2, city, state (2-letter), postalCode (80202 or 80202-1234).
email, phone no Kept with the order. Nothing is sent to them.
driversLicense for driving-record levels { "number": "...", "state": "CO" }.

The certification

Every order carries one. As the user of the report you are responsible for these steps, and you certify them per order:

  • permissiblePurpose: employment (hiring, promotion, retention, or engaging a contractor) or volunteer.
  • disclosureProvided: true means you gave the applicant a clear written disclosure, in a document consisting only of that disclosure, that a background check may be obtained.
  • authorizationObtained: true means you have the applicant's written authorization, and keep it.

An order without a complete certification is 400 validation_failed and nothing is billed. We keep the purpose and the time you certified with the check.

Job location

Some levels need jobLocation (for example Denver, CO) and stateOfEmployment (CO): where the applicant will work, which decides the state rules that apply. Send both or neither, and only what you actually know.

Duplicates and retries

  • An order is 409 duplicate_check (nothing ordered or billed) when the same externalIdentifier, or the same applicant (matched by SSN) on the same account, already has a check in progress (PENDING, CREATED) or completed and not expired (CLEAR, REVIEW). The body carries the existing check in order.
  • forceReorder: true orders anyway and is billed as a new check.
  • Always send an externalIdentifier and reuse it when you retry after a timeout or network error. A retry never creates a second check: you get the check, or 409 duplicate_check with it (your first attempt worked).
  • A 502 whose body is a check (status PENDING) means the order was sent but its answer was lost. Don't use forceReorder. It resolves on its own within about an hour and you get check.updated.

Handling applicant data

  • Send orders from your servers only, over TLS. The API accepts nothing else.
  • Don't log request bodies, and keep the SSN out of your own error reports.
  • We pass the details to the screening provider for that one order and never store or log them. The SSN is kept only as a keyed one-way hash, so duplicate orders can be caught. Our error messages name the fields that failed and never repeat the values you sent.

5. Products and prices

GET /products lists every level with its name, description and your price. GET /clients/{clientRef}/products lists what one account or sub-account can order. Show prices with their feesNote ("Plus pass-through fees."): some jurisdictions charge court or state fees that are passed through at cost and appear in charges once a check finishes.

6. Tracking a check

status Meaning Final?
PENDING Being submitted, or its submission's answer was lost (resolves on its own). no
CREATED Submitted; searches are running. stage is in_progress. no
CLEAR Complete, and no search found records. yes
REVIEW Complete, with records to review. yes
ERROR Couldn't be completed. errorCode and errorMessage say why. yes
CANCELLED Cancelled by you. yes
  • CLEAR and REVIEW checks carry completedAt, expirationDate (completion plus checkValidityYears, default 2; set it with PUT /settings or per sub-account with PUT /clients/{clientRef}/settings) and reportUrl, the report in the Datasource portal.
  • Finished checks carry charges (US dollars).
  • Read one check with GET /clients/{clientRef}/checks/{checkRef} (checkRef is our id or your externalIdentifier). Cancel one that is still open with POST .../{checkRef}/cancel.
  • Reconcile with GET /clients/{clientRef}/checks?updated_since=...&limit=500, following nextCursor until it is null. Do it nightly for account and every sub-account: it catches anything a webhook missed.

7. Webhooks

Register https:// URL with PUT /settings. Every event is a POST of a flat JSON object with event, eventId and timestamp:

  • check.updated: a check's status or stage changed. Carries the check.
  • check.expiring: 30 days and again 7 days before a CLEAR or REVIEW check's expirationDate.
  • ping: sent by POST /settings/webhook-test.

Verify every delivery. Compute HMAC-SHA256 over the raw body (the exact bytes, before parsing), keyed with the UTF-8 bytes of your whole webhook secret (whsec_ prefix included), hex-encode it in lowercase, and compare it with X-Partner-Signature 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", ""))

Answer with any 2xx within 15 seconds and do slow work afterwards. Anything else is retried up to 5 more times with increasing delays. Delivery is at least once and unordered: a retry resends the identical body (same eventId and timestamp), so drop repeats by eventId, and keep the state with the latest timestamp for each check. GET /settings/webhook-deliveries shows the last 30 days of attempts and how your endpoint answered. Rotate the secret with POST /settings/webhook-secret (the old one stops working at once).

8. Errors

Every error is RFC 9457 problem details with a stable code and a type link to its entry in the error catalog. Branch on the HTTP status, then on code; never on title or detail.

10. Going live

  1. Run your test integration end to end: an order, every status through the simulator, signature checks, a nightly reconciliation.
  2. Ask Datasource for your pk_live_ key and set your production webhook URL with PUT /settings on https://api.datasourcecorp.app (settings are per environment).
  3. Make sure your disclosure and authorization flow is the one you certify on every order.

11. Limits

What Limit
externalIdentifier, externalClientId 100 characters
Names 100 characters each
Page size (limit) 1 to 500
Webhook answer 15 seconds
Invalid-key requests from one IP 20 a minute, then 429 for a minute

12. Versioning and changes

Changes are additive: new fields, new optional parameters, new webhook events, new enum values. Ignore what you don't know. A breaking change gets 90 days' notice in the changelog and by email.

13. Compliance

Datasource is the consumer reporting agency. You are the user of the report: you certify a permissible purpose on every order, give the applicant a standalone written disclosure and get their written authorization before you order, and you make, and are responsible for, every decision based on a report. That includes the adverse action process: a pre-adverse action notice with a copy of the report and the summary of rights before you decide, and an adverse action notice after. State and local rules (for example ban-the-box and fair chance laws) can add steps; follow the rules where the applicant will work.