# checkr-tenant

Integrate the Checkr Tenant API into this project: place screening orders, receive results, and verify it end-to-end in test mode. Follow these steps in order, and stop where a step tells you to stop.

SCOPE: Backend only (API client, order creation, result delivery, persistence, tests). Build UI only if I ask. Never place live orders.

0. GROUND RULES. These apply to every step.

   - Use only `ckr_sk_test_…` keys. Never send a request with a `ckr_sk_live_…` key; live orders are real, billed screenings of real people. If a live key is in the environment you'd use, stop and tell me.
   - Keep keys out of chat and code. Have me put them in environment variables or my secrets manager. Don't ask me to paste a key, never hardcode or commit one, and never write one to a config file that could be committed.
   - Applicant data is sensitive (SSN, DOB, criminal, credit, eviction, income). Never log request or report bodies; log ids (`ord_…`, `rp_…`, `evt_…`) instead.
   - Landlords decide, code doesn't. Show results to the landlord; `consider` means "review before deciding", not "adverse". Don't build automatic approve/deny logic unless I ask, and if I do, tell me screening decisions carry legal obligations (for example FCRA adverse action notices) to review with counsel.
   - If this file disagrees with the OpenAPI spec, follow the spec and tell me. If something isn't in either, ask me or list it as an open question. Don't invent endpoints, fields, or values.

1. PREREQUISITES. You can't do these for me. Ask about all three at once and stop until I confirm.
   a. API access is enabled on my Checkr Tenant organization. API access is free; screenings are paid per order, or the applicant pays. To enable it: create an account at https://tenant.checkr.com/signup, complete identity vetting once at https://tenant.checkr.com/verify, then request API access in the chat on https://tenant.checkr.com/developers. Checkr then turns on Developer settings.
   b. A test API key exists in https://tenant.checkr.com/settings/developer and is saved in an environment variable (suggest `CHECKR_TENANT_API_KEY`, or follow the project's convention). The full key is shown once.
   c. A payment method is saved in https://tenant.checkr.com/settings/billing. Orders without one fail with `402 no_payment_method`. Test orders aren't billed, but add a card before testing.

2. SPEC. Fetch the OpenAPI description. It is the source of truth for every path, field, enum, package, and response shape; don't build from memory.

   - https://checkr-tenant-api-docs.redocly.app/_bundle/v1.json?download
   - https://checkr-tenant-api-docs.redocly.app/_bundle/v1.yaml?download
     If you can't fetch either, ask me to download one into the project. If the project already generates clients from OpenAPI, offer to do the same; otherwise write a small hand-rolled client.
     Base URL `https://tenant.checkr.com/api`. Auth is `Authorization: Bearer <key>`. Send `Checkr-Tenant-Version: 2026-04-29` on every request. Packages are `starter`, `essential`, `complete`. Mode comes from the key prefix, and test ids contain `_test_`. Never hardcode prices; they vary by customer, so ask me how to display them.

3. VERIFY THE KEY. Call `GET /orders?per_page=1` and report only the status code, never the key or body. `200`: continue. `401`: key missing, wrong, or revoked; stop. `403`: API access probably isn't enabled yet; stop.

4. PLAN. Inspect the project and lead with your best guess (language, framework, existing HTTP client, config and secrets loading, test framework and HTTP mocking, job system, public HTTPS endpoint, existing models to link to Checkr ids). Follow its conventions and name any new dependency before adding it. In a monorepo, say which package you chose and why. Then confirm these in one message, with a recommendation for each:
   a. Who the screening is for. Own organization: omit `original_requester`. Platform serving other landlords: use one account and key, and send `original_requester` on every order with the customer's real legal name.
   b. What triggers an order, which packages to offer, and whether to offer the `identity_verification` add-on.
   c. Result delivery: webhooks (recommended, needs a public HTTPS endpoint) or polling.
   d. Whether the app already collects applicant DOB and SSN securely. If not, omit them and let Checkr collect them from the applicant (recommended).
   Write a short plan (files, env vars, data to persist) and stop until I approve it.

5. BUILD THE CLIENT. One module, following project conventions:

   - base URL and key come from config, never literals
   - parse the error envelope `{ errors: [{ code, detail, source: { pointer } }] }` into a typed error; branch on `code`, never `detail`
   - retry `500` and network failures with capped backoff; never retry `401`, `402`, `403`, `404`, or `422`
   - if a `429` is ever returned (rate limits aren't documented), back off, honoring `Retry-After` when present
   - `402` codes: `no_payment_method` and `authentication_required` need me to fix billing; `card_declined` needs a new `Idempotency-Key` on retry
   - follow `next_href` for pagination (set `per_page`, 1 to 100); never build cursors
   - read timeout of at least 60 s for `GET /reports/{id}/pdf`

6. BUILD THE ORDER FLOW. `POST /orders` with `package`, a `property`, and an `applicant` (`email` plus `first_name` and `last_name`), or `property_id` / `applicant_id` instead (never both for the same one). The spec has the full field list.

   - Each applicant attaches to at most one order. Create a new applicant per order; don't cache Checkr applicant ids against people.
   - `Idempotency-Key` is required. Create the local order record with a new UUIDv4 key and save it before calling, and reuse that key when retrying a `5xx` or network failure of the same order.
   - Send `dob` and `ssn` only if I chose to in step 4.
   - `payer: "applicant"` is live mode only. If I enable it, point me to https://tenant.checkr.com/resources/application-screening-fee because many states cap applicant screening fees.
   - Persist the order `id`, `status`, `live_mode`, and `application_url` (the apply link, set only while the order is `waiting_for_applicant`). The report id arrives later.
   - Map `422` entries to the app's validation errors using `source.pointer`.

7. BUILD RESULT DELIVERY. Reports run asynchronously after the applicant submits.

   - Webhooks: I register endpoints in Developer settings; you can't. Each is bound to one mode, receives every event, and shows its signing secret once (store as `CHECKR_TENANT_WEBHOOK_SECRET`). The URL must be public HTTPS; for local work I need a tunnel or preview deployment, so ask which and don't install tunnel software without asking. Events: `order.applicant.visited|started|completed|paid`, `report.product.completed`, `report.completed`. Rely on `report.completed`: call `GET /reports/{id}`, store it against the local order via `order_id`, and mark it complete. The receiver must:
     1. keep the raw body (no JSON parsing before you read the bytes)
     2. return 2xx and do nothing for the dashboard Test probe (empty body, no signature)
     3. verify `Tenant-Signature: t=<unix>,v1=<hex>`: HMAC-SHA256 of `<t>.<raw_body>` with the signing secret, hex-encoded, compared in constant time against every `v1`; return `400` if missing or no match
     4. reject stale timestamps; the tolerance isn't documented, so make it configurable and agree a value with me
     5. dedupe on event `id` (stable across retries) and return 2xx for duplicates
     6. return 2xx fast and enqueue the work; non-2xx responses are retried with backoff
     7. ignore unknown event types with 2xx, and use `created_at` rather than arrival order
   - Polling: per open order, call `GET /orders/{id}/report` with backoff and a maximum duration; `404` means not ready. Done when every non-null product has a non-null `status`.
   - Reports: products not on the order are `null`; item `status` is `clear`, `consider`, or `null` while assembling. Show `consider` results with their details; don't summarize them away.

8. TEST IN TEST MODE. Test orders are free and return canned results chosen by the applicant's first name, last name, DOB, and SSN; all four must match or you get the clean scenario. These SSNs are reserved test values. All profiles use DOB `1996-04-27`, and any valid address works. Scenario tests must send `dob` and `ssn`, and need the `complete` package so every product appears.

   | First       | Last     | SSN         | Consider on                                                    |
   | ----------- | -------- | ----------- | -------------------------------------------------------------- |
   | Herbert     | Humphrey | 111-11-1111 | criminal                                                       |
   | Madelyn     | Webster  | 222-22-2222 | credit                                                         |
   | Norma       | Davies   | 333-33-3333 | eviction                                                       |
   | Tim         | Watkins  | 444-44-4444 | all five (criminal, credit, eviction, sex offender, watchlist) |
   | Hudson      | Green    | 555-55-5555 | nothing (runs the real applicant flow)                         |
   | anyone else |          |             | nothing                                                        |

   - Most profiles skip the applicant flow: no email or SMS, `application_url` is `null`, and the report is produced right away. Still fetch it asynchronously, polling with a timeout.
   - Hudson Green runs the real applicant flow and emails the given address, so use an inbox I control.
   - `payer: "applicant"` returns `422` in test mode, and `Idempotency-Key` doesn't replay, so unit-test key reuse instead of testing it against the API.
   - Test-mode webhook endpoints only receive test-mode events.

   Write and run:

   1. Client unit tests with the project's HTTP mocking: headers, error parsing including `402` codes, retry rules, pagination, idempotency-key reuse.
   2. Signature unit tests: valid, tampered body, wrong secret, missing header, stale timestamp, empty probe, duplicate event id.
   3. Integration tests that run only when the key env var is set and starts with `ckr_sk_test_`: a Herbert Humphrey `complete` order (id contains `_test_`, `live_mode` is `false`, report reached by polling, `criminal_history` is `consider`, the other screening products are `clear`), a `starter` order (`credit_report`, `eviction_history`, `income_verification` are `null`), and for platforms an order with `original_requester` that returns an `original_requester_id`.
   4. Webhook end-to-end, if I have a public URL and a test endpoint: confirm `report.completed` arrives, verifies, and triggers the report fetch.
      Report results. If something fails, fix it or explain exactly what's blocking.

9. CUTOVER. Don't do this yourself. Give me a checklist adapted to my stack: live key in the production secrets store only; a separate live webhook endpoint with its own signing secret; production loads live values and every other environment loads test values; a saved payment method and `402` errors surfaced to whoever can fix billing; logs and error trackers don't capture request or report bodies; idempotency keys saved before each create and reused on retry; platforms send the correct `original_requester`; landlords see `consider` results with details; first live order placed manually while watching the webhook and report fetch; screening-decision obligations reviewed with counsel.

10. REPORT. Tell me briefly: files created or changed and why; environment variables per environment; how to run the tests and the latest results; what I still do by hand (key creation, webhook registration, tunnel, cutover); and open questions or any mismatch between this file and the spec.

More: https://checkr-tenant-api-docs.redocly.app/ (also `/testing`, `/webhooks`, `/errors`, `/mcp-server`).
