{"info":{"_postman_id":"363a596e-9cb7-463d-ad41-0d544d48ee3a","name":"PSBI Partner API","description":"<html><head></head><body><h1 id=\"psbi-partner-api\">PSBI Partner API</h1>\n<p>Build background screening and ongoing compliance into your HRIS, ATS or workforce platform.</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Part</th>\n<th>What it does</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><strong>AutoPilot API</strong></td>\n<td>Enroll a person into a position (a <em>checklist</em>). PSBI orders everything that position requires and keeps it current.</td>\n</tr>\n<tr>\n<td><strong>Partner Gateway</strong></td>\n<td>Order specific background check products for a person.</td>\n</tr>\n<tr>\n<td><strong>Status Webhooks</strong></td>\n<td>PSBI posts to your endpoint when an order is in review, in progress and complete.</td>\n</tr>\n</tbody>\n</table>\n</div><p><strong>New integration?</strong> Start with <strong>Recipe: AutoPilot enrollment with status webhooks</strong>. It walks through a working integration from the first API call to the completed report.</p>\n<p><strong>Using an AI assistant or coding agent?</strong> Point it at the plain-markdown version: <a href=\"https://psbi.com/developers/partner-api.md\">psbi.com/developers/partner-api.md</a>, with the recipe at <a href=\"https://psbi.com/developers/autopilot-webhooks-recipe.md\">psbi.com/developers/autopilot-webhooks-recipe.md</a>. Both are indexed from <a href=\"https://psbi.com/llms.txt\">psbi.com/llms.txt</a>.</p>\n<h2 id=\"base-url\">Base URL</h2>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>https://app.psbi.com\n</code></pre><h2 id=\"authentication\">Authentication</h2>\n<p>Every request carries your API key. Send it in a header:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>X-API-Key: your-api-key\n</code></pre><p><code>Authorization: Bearer your-api-key</code> is also accepted.</p>\n<ul>\n<li>A key belongs to one connection, and a connection belongs to one PSBI client account. Every request acts on that account only.</li>\n<li>Keep keys server-side. A key is a credential for your clients' screening data.</li>\n<li>If the system calling PSBI cannot set headers, an <code>api_key</code> query parameter is accepted. Prefer a header wherever you can, because URLs end up in logs.</li>\n</ul>\n<p>Your PSBI implementation contact issues keys.</p>\n<h2 id=\"requests\">Requests</h2>\n<ul>\n<li>Send JSON with <code>Content-Type: application/json</code>, and <code>Accept: application/json</code>.</li>\n<li>Dates are <code>YYYY-MM-DD</code>. Timestamps in responses are ISO 8601 in UTC.</li>\n</ul>\n<h2 id=\"responses\">Responses</h2>\n<p>Every response has a boolean <code>success</code>. Failures add an <code>error</code> object:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"VALIDATION_FAILED\",\n    \"message\": \"The provided data is invalid\",\n    \"details\": { \"consumer.email\": [\"Consumer email is required\"] }\n  }\n}\n</code></pre>\n<p><code>details</code> is included when there is more to say.</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>HTTP</th>\n<th><code>error.code</code></th>\n<th>Meaning</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>400</td>\n<td><code>NO_CLIENT_ASSOCIATION</code></td>\n<td>The API key is not attached to a client account. Contact PSBI.</td>\n</tr>\n<tr>\n<td>401</td>\n<td><code>MISSING_API_KEY</code></td>\n<td>No API key was sent.</td>\n</tr>\n<tr>\n<td>401</td>\n<td><code>INVALID_API_KEY</code></td>\n<td>The key is wrong or has been deactivated.</td>\n</tr>\n<tr>\n<td>404</td>\n<td><code>TRANSACTION_NOT_FOUND</code></td>\n<td>No request with that <code>external_id</code> exists on your connection.</td>\n</tr>\n<tr>\n<td>404</td>\n<td><code>CHECKLIST_NOT_FOUND</code></td>\n<td>The checklist does not exist, is inactive, or belongs to another account.</td>\n</tr>\n<tr>\n<td>404</td>\n<td><code>CONSUMER_NOT_FOUND</code></td>\n<td>A transition could not match the person.</td>\n</tr>\n<tr>\n<td>404</td>\n<td><code>NO_ACTIVE_ENROLLMENT</code></td>\n<td>A transition found the person, but they are not enrolled in a position.</td>\n</tr>\n<tr>\n<td>409</td>\n<td><code>DUPLICATE_TRANSACTION</code></td>\n<td>That <code>external_id</code> has already been used on your connection.</td>\n</tr>\n<tr>\n<td>422</td>\n<td><code>VALIDATION_FAILED</code></td>\n<td>The body failed validation. <code>details</code> lists each field.</td>\n</tr>\n<tr>\n<td>422</td>\n<td>varies</td>\n<td>A synchronous (<code>\"sync\": true</code>) request was valid but could not be processed, for example <code>already_enrolled</code>. Read <code>error.message</code>.</td>\n</tr>\n<tr>\n<td>500</td>\n<td><code>INTERNAL_ERROR</code></td>\n<td>Something failed on PSBI's side. Retry later, and contact PSBI if it persists.</td>\n</tr>\n</tbody>\n</table>\n</div><h2 id=\"identifiers\">Identifiers</h2>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Whose</th>\n<th>Notes</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>external_id</code></td>\n<td><strong>Yours</strong></td>\n<td>Your reference for the request, such as a hire or requisition id. It must be unique per connection, and reusing one returns <code>409</code>. You look requests up by it, and it comes back on every status webhook. If you leave it out, PSBI generates one and returns it, but you should always send your own.</td>\n</tr>\n<tr>\n<td><code>transaction_id</code></td>\n<td>PSBI</td>\n<td>The id of your API request in PSBI.</td>\n</tr>\n<tr>\n<td><code>consumer_transaction_id</code></td>\n<td>PSBI</td>\n<td>The id of the background check order the request produced.</td>\n</tr>\n<tr>\n<td><code>consumer.id</code></td>\n<td>PSBI</td>\n<td>The person's id in PSBI. Use it for transitions.</td>\n</tr>\n</tbody>\n</table>\n</div><h2 id=\"synchronous-and-asynchronous-processing\">Synchronous and asynchronous processing</h2>\n<ul>\n<li><strong>Asynchronous (default).</strong> PSBI validates the request, returns <code>202 Accepted</code> with <code>status: \"received\"</code>, and processes it in the background. Processing normally completes within a minute. Poll the status endpoint or wait for webhooks.</li>\n<li><strong>Synchronous (<code>\"sync\": true</code>).</strong> PSBI processes the request before responding and returns <code>200</code> with the result. Allow up to 30 seconds. This suits interactive screens. For bulk loads, use asynchronous.</li>\n</ul>\n<p>Validation errors, unknown checklists and duplicate <code>external_id</code>s are rejected immediately in both modes.</p>\n<h2 id=\"volume\">Volume</h2>\n<p>There is no published per-minute limit. Before loading records in bulk (thousands at once), tell your implementation contact.</p>\n<h2 id=\"support\">Support</h2>\n<p>Contact your PSBI implementation team, or email <a href=\"mailto:integrations@psbi.com\">integrations@psbi.com</a>.</p>\n</body></html>","schema":"https://schema.getpostman.com/json/collection/v2.0.0/collection.json","toc":[{"content":"PSBI Partner API","slug":"psbi-partner-api"}],"owner":"44933307","collectionId":"363a596e-9cb7-463d-ad41-0d544d48ee3a","publishedId":"2sBXVbJuYQ","public":true,"customColor":{"top-bar":"FFFFFF","right-sidebar":"303030","highlight":"5643AD"},"publishDate":"2025-12-31T20:25:08.000Z"},"item":[{"name":"Authentication","item":[{"name":"Test Authentication","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"API key accepted\", function () {","    pm.expect(pm.response.code).to.equal(404);","    pm.expect(pm.response.json().error.code).to.equal('TRANSACTION_NOT_FOUND');","});"]}}],"id":"6467233d-e31f-4763-94fa-83c275acb280","request":{"method":"GET","header":[{"key":"X-API-Key","value":"your-api-key-here","type":"text","description":"<p>Your API key</p>\n"},{"key":"Accept","value":"application/json","type":"text"}],"url":"https://app.psbi.com/api/v1/partners/transactions/test-auth-check","description":"<p>Confirm your API key works before sending real requests.</p>\n<p>This looks up a request id that does not exist, so a working key returns <strong><code>404 TRANSACTION_NOT_FOUND</code></strong>. That 404 means you are authenticated.</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Response</th>\n<th>Meaning</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>404 TRANSACTION_NOT_FOUND</code></td>\n<td>The key is valid.</td>\n</tr>\n<tr>\n<td><code>401 INVALID_API_KEY</code></td>\n<td>The key is wrong or inactive.</td>\n</tr>\n<tr>\n<td><code>401 MISSING_API_KEY</code></td>\n<td>No key reached PSBI. Check the header name.</td>\n</tr>\n</tbody>\n</table>\n</div>","urlObject":{"path":["api","v1","partners","transactions","test-auth-check"],"host":["https://app.psbi.com"],"query":[],"variable":[]}},"response":[{"id":"eda24081-8787-4c00-99dc-9c857e7f102b","name":"Key is valid","originalRequest":{"method":"GET","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"url":"https://app.psbi.com/api/v1/partners/transactions/test-auth-check"},"status":"Not Found","code":404,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"TRANSACTION_NOT_FOUND\",\n    \"message\": \"Transaction not found\"\n  }\n}"},{"id":"15aef297-bf6a-4fd3-a316-6d9a39723ba5","name":"Key is invalid","originalRequest":{"method":"GET","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"url":"https://app.psbi.com/api/v1/partners/transactions/test-auth-check"},"status":"Unauthorized","code":401,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"INVALID_API_KEY\",\n    \"message\": \"Invalid API key provided\"\n  }\n}"},{"id":"234a2b5e-7d3d-4bb3-be8d-afa271ef9737","name":"Key is missing","originalRequest":{"method":"GET","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"url":"https://app.psbi.com/api/v1/partners/transactions/test-auth-check"},"status":"Unauthorized","code":401,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"MISSING_API_KEY\",\n    \"message\": \"API key is required. Provide via X-API-Key header or Authorization: Bearer token\"\n  }\n}"}],"_postman_id":"6467233d-e31f-4763-94fa-83c275acb280"}],"id":"b331d066-61eb-4378-a3f0-5215c29a4c9c","description":"<p>Check your API key before sending real requests.</p>\n","_postman_id":"b331d066-61eb-4378-a3f0-5215c29a4c9c"},{"name":"Recipe: AutoPilot enrollment with status webhooks","item":[{"name":"Step 1: Verify your API key","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"API key accepted\", function () {","    pm.expect(pm.response.code).to.equal(404);","    pm.expect(pm.response.json().error.code).to.equal('TRANSACTION_NOT_FOUND');","});"]}}],"id":"48c13281-40b9-4812-a3ab-436ee5a881bd","request":{"method":"GET","header":[{"key":"X-API-Key","value":"your-api-key-here","type":"text","description":"<p>Your API key</p>\n"},{"key":"Accept","value":"application/json","type":"text"}],"url":"https://app.psbi.com/api/v1/partners/transactions/test-auth-check","description":"<p>A working key returns <code>404 TRANSACTION_NOT_FOUND</code>. Any <code>401</code> means the key or header is wrong.</p>\n","urlObject":{"path":["api","v1","partners","transactions","test-auth-check"],"host":["https://app.psbi.com"],"query":[],"variable":[]}},"response":[],"_postman_id":"48c13281-40b9-4812-a3ab-436ee5a881bd"},{"name":"Step 2: Enroll the new hire","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Enrollment accepted\", function () {","    pm.expect([200, 202]).to.include(pm.response.code);","    pm.expect(pm.response.json().success).to.be.true;","});","","var json = pm.response.json();","if (json.external_id) { pm.collectionVariables.set('last_autopilot_external_id', json.external_id); }","if (json.consumer && json.consumer.id) { pm.collectionVariables.set('last_consumer_id', json.consumer.id); }"]}}],"id":"f6e9b7db-12f5-48a0-9aa8-d435fc40085f","request":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key-here","type":"text","description":"<p>Your API key</p>\n"},{"key":"Content-Type","value":"application/json","type":"text"},{"key":"Accept","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"external_id\": \"HR-1789977182\",\n  \"consumer\": {\n    \"first_name\": \"Ophelia\",\n    \"last_name\": \"Denesik\",\n    \"email\": \"you+psbi-test@example.com\",\n    \"phone\": \"555-123-4567\",\n    \"state\": \"TX\"\n  },\n  \"checklist_code\": \"Caregiver\",\n  \"enrollment_path\": \"email_onboarding\",\n  \"metadata\": {\n    \"requisition_id\": \"REQ-2231\"\n  }\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/autopilot/enroll","description":"<p>Enroll into the position on the <code>email_onboarding</code> path. The person receives an invitation, and the order is submitted once they finish it.</p>\n<p>Set <code>external_id</code> to something that leads back to your record. The test script saves it for Step 3.</p>\n<p>Every field and path is documented under <strong>AutoPilot API → Enroll Consumer</strong>.</p>\n","urlObject":{"path":["api","v1","partners","autopilot","enroll"],"host":["https://app.psbi.com"],"query":[],"variable":[]}},"response":[{"id":"fb2edd52-cae6-4780-ab15-f2cb2e3763fe","name":"Accepted (async)","originalRequest":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"url":"https://app.psbi.com/api/v1/partners/autopilot/enroll"},"status":"Accepted","code":202,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"transaction_id\": 1234,\n  \"external_id\": \"HR-2026-001234\",\n  \"status\": \"received\",\n  \"message\": \"Enrollment request received and queued for processing\",\n  \"received_at\": \"2026-09-15T14:02:11.000000Z\"\n}"}],"_postman_id":"f6e9b7db-12f5-48a0-9aa8-d435fc40085f"},{"name":"Step 3: Check the enrollment","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Status retrieved\", function () {","    pm.expect([200, 404]).to.include(pm.response.code);","});"]}}],"id":"0e5a6b7d-114e-494e-9a70-ceb4008c2065","request":{"method":"GET","header":[{"key":"X-API-Key","value":"your-api-key-here","type":"text","description":"<p>Your API key</p>\n"},{"key":"Accept","value":"application/json","type":"text"}],"url":"https://app.psbi.com/api/v1/partners/autopilot/enrollment/","description":"<p>Confirms the request was <code>processed</code> and an order exists. A <code>failed</code> request carries <code>error_message</code>.</p>\n","urlObject":{"path":["api","v1","partners","autopilot","enrollment",""],"host":["https://app.psbi.com"],"query":[],"variable":[]}},"response":[{"id":"6ef0cbe3-bc1f-412e-807d-3093d97c25d3","name":"Processed","originalRequest":{"method":"GET","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"url":"https://app.psbi.com/api/v1/partners/autopilot/enrollment/HR-2026-001234"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"transaction\": {\n    \"id\": 1234,\n    \"external_id\": \"HR-2026-001234\",\n    \"type\": \"autopilot_enroll\",\n    \"status\": \"processed\",\n    \"received_at\": \"2026-09-15T14:02:11.000000Z\",\n    \"processed_at\": \"2026-09-15T14:02:43.000000Z\",\n    \"error_message\": null\n  },\n  \"enrollment\": {\n    \"id\": 108,\n    \"consumer_id\": 54321,\n    \"consumer_created\": true,\n    \"search_count\": 0,\n    \"pending_requirements\": true\n  },\n  \"consumer_transaction\": {\n    \"id\": 98765,\n    \"status\": \"draft\",\n    \"submitted_at\": null,\n    \"consumer\": {\n      \"id\": 54321,\n      \"name\": \"John Doe\"\n    }\n  }\n}"},{"id":"61ec306b-eb3e-40a0-b97d-b4b9c068a7bf","name":"Failed","originalRequest":{"method":"GET","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"url":"https://app.psbi.com/api/v1/partners/autopilot/enrollment/HR-2026-001299"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"transaction\": {\n    \"id\": 1299,\n    \"external_id\": \"HR-2026-001299\",\n    \"type\": \"autopilot_enroll\",\n    \"status\": \"failed\",\n    \"received_at\": \"2026-09-15T14:02:11.000000Z\",\n    \"processed_at\": \"2026-09-15T14:02:43.000000Z\",\n    \"error_message\": \"Enrollment created but transaction failed: No requirements found for this checklist\"\n  }\n}"}],"_postman_id":"0e5a6b7d-114e-494e-9a70-ceb4008c2065"}],"id":"f05112c2-2ca9-48cf-8305-963549f7a577","description":"<h1 id=\"recipe-autopilot-enrollment-with-status-webhooks\">Recipe: AutoPilot enrollment with status webhooks</h1>\n<p>Enroll a new hire into their position and keep your system updated as their background check moves from <strong>review</strong> to <strong>in progress</strong> to <strong>complete</strong>, all without polling.</p>\n<p><strong>You'll need:</strong> an API key, the checklist (position) names or ids for your client, and an HTTPS endpoint that can receive webhooks.</p>\n<h2 id=\"how-it-fits-together\">How it fits together</h2>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code>Your system                          PSBI\n───────────                          ────\nPOST /autopilot/enroll ───────────▶  202 Accepted (status: received)\n                                     person is matched or created, enrolled, order created\n                                     person completes the email invitation\n            ◀─────────── webhook     review       information collected, awaiting submission\n            ◀─────────── webhook     in_progress  searches running\n            ◀─────────── webhook     complete     result: clear | needs_review\n            ◀─────────── webhook     complete     report_url: the PDF is ready\n</code></pre><h2 id=\"1-get-set-up-with-psbi\">1. Get set up with PSBI</h2>\n<p>Your implementation contact provides:</p>\n<ul>\n<li>an <strong>API key</strong> for the client account;</li>\n<li>the <strong>checklists</strong> you will enroll into, with their exact names or ids, such as <code>Caregiver</code> and <code>Driver</code>;</li>\n<li>a <strong>signing secret</strong>, once you've sent them your webhook URL.</li>\n</ul>\n<p>Decide on an <strong><code>external_id</code></strong> scheme. It should be unique per request and should lead back to your record, for example <code>&lt;your record id&gt;-&lt;attempt&gt;</code>. Every webhook carries it back to you.</p>\n<h2 id=\"2-verify-your-key\">2. Verify your key</h2>\n<p>Run <strong>Step 1: Verify your API key</strong>. A <code>404 TRANSACTION_NOT_FOUND</code> means the key works.</p>\n<h2 id=\"3-build-the-webhook-receiver-first\">3. Build the webhook receiver first</h2>\n<p>Build it before you enroll anyone, so the first events aren't missed. Your endpoint should:</p>\n<ol>\n<li><strong>Verify <code>X-P51-Signature</code>.</strong> It is an HMAC-SHA256 of the raw body with your signing secret, and <strong>Status Webhooks</strong> has code for it.</li>\n<li><strong>Return 2xx quickly</strong>, within 30 seconds. Queue the work.</li>\n<li><strong>Look the record up by <code>external_id</code>.</strong></li>\n<li><strong>Apply the update idempotently.</strong> Retries can repeat an event, and order isn't guaranteed.</li>\n</ol>\n<h2 id=\"4-enroll\">4. Enroll</h2>\n<p>Run <strong>Step 2: Enroll the new hire</strong>. The request is asynchronous and returns <code>202</code> with your <code>external_id</code>:</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"success\": true,\n  \"transaction_id\": 1234,\n  \"external_id\": \"HR-2026-001234\",\n  \"status\": \"received\",\n  \"message\": \"Enrollment request received and queued for processing\",\n  \"received_at\": \"2026-09-15T14:02:11.000000Z\"\n}\n</code></pre>\n<p>Store the mapping from <code>external_id</code> to your record. If you get <code>409 DUPLICATE_TRANSACTION</code>, that <code>external_id</code> is already in use; don't reuse ids across attempts.</p>\n<h2 id=\"5-confirm-it-processed-optional\">5. Confirm it processed (optional)</h2>\n<p>Run <strong>Step 3: Check the enrollment</strong> about a minute later.</p>\n<ul>\n<li><strong><code>transaction.status: \"processed\"</code> with a <code>consumer_transaction</code>.</strong> An order exists and webhooks will follow.</li>\n<li><strong><code>transaction.status: \"failed\"</code>.</strong> <code>error_message</code> says why. Fix the problem and enroll again with a <strong>new</strong> <code>external_id</code>.</li>\n</ul>\n<h2 id=\"6-handle-the-status-webhooks\">6. Handle the status webhooks</h2>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th><code>status</code></th>\n<th><code>result</code></th>\n<th>Suggested state in your system</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>review</code></td>\n<td></td>\n<td>Awaiting submission</td>\n</tr>\n<tr>\n<td><code>in_progress</code></td>\n<td></td>\n<td>Screening in progress</td>\n</tr>\n<tr>\n<td><code>complete</code></td>\n<td><code>clear</code></td>\n<td>Screening complete: clear</td>\n</tr>\n<tr>\n<td><code>complete</code></td>\n<td><code>needs_review</code></td>\n<td>Screening complete: client review needed</td>\n</tr>\n</tbody>\n</table>\n</div><p>On <code>consumer.transaction.report_generated</code>, download <code>report_url</code> and attach the PDF to the person's record.</p>\n<p>Two things to know:</p>\n<ul>\n<li><strong><code>review</code> happens before searches run.</strong> It means the order is ready to submit. The outcome of the searches only arrives on <code>complete</code>.</li>\n<li><strong><code>needs_review</code> is not a decision.</strong> It means a person at the client needs to look at a result in PSBI. Any adverse action runs through PSBI's FCRA workflow, not through the API.</li>\n</ul>\n<h2 id=\"7-test-it-end-to-end\">7. Test it end to end</h2>\n<ol>\n<li>Point the webhook URL at a request inspector, such as webhook.site, or your staging receiver.</li>\n<li>Enroll a test person with <strong>your own email address</strong> on the <code>email_onboarding</code> path.</li>\n<li>Complete the invitation email. You should receive <strong>review</strong>, then <strong>in progress</strong>.</li>\n<li>Your implementation contact can take a test order through to <strong>complete</strong> and <strong>report ready</strong>.</li>\n<li>Check signature verification against the real deliveries, not just the examples here.</li>\n</ol>\n<h2 id=\"going-live-checklist\">Going live checklist</h2>\n<ul>\n<li> Production API key stored server-side.</li>\n<li> Webhook URL registered, and signing secret stored.</li>\n<li> Signature verified on every request, and mismatches rejected.</li>\n<li> Receiver acknowledges within 30 seconds and tolerates duplicates.</li>\n<li> <code>external_id</code> is unique per request.</li>\n<li> Reports downloaded from <code>report_url</code> and stored securely.</li>\n<li> Failed enrollments surfaced to a person (step 5).</li>\n</ul>\n","_postman_id":"f05112c2-2ca9-48cf-8305-963549f7a577"},{"name":"AutoPilot API","item":[{"name":"Enrollment","item":[{"name":"Enroll Consumer (Minimal)","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Enrollment accepted\", function () {","    pm.expect([200, 202]).to.include(pm.response.code);","    pm.expect(pm.response.json().success).to.be.true;","});","","var json = pm.response.json();","if (json.external_id) { pm.collectionVariables.set('last_autopilot_external_id', json.external_id); }","if (json.consumer && json.consumer.id) { pm.collectionVariables.set('last_consumer_id', json.consumer.id); }"]}}],"id":"53ed660d-7e96-4567-9313-dd43ab8def33","request":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key-here","type":"text","description":"<p>Your API key</p>\n"},{"key":"Content-Type","value":"application/json","type":"text"},{"key":"Accept","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"external_id\": \"HR-1789977182\",\n  \"consumer\": {\n    \"first_name\": \"Duncan\",\n    \"last_name\": \"Aufderhar\",\n    \"email\": \"Eleanora_Jones@gmail.com\"\n  },\n  \"checklist_code\": \"Caregiver\"\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/autopilot/enroll","description":"<p>Enroll a person into a position with only the required fields.</p>\n<p>Defaults that apply here:</p>\n<ul>\n<li><strong>Path:</strong> the account default, normally <code>email_onboarding</code>, so the person receives an email invitation.</li>\n<li><strong>Processing:</strong> asynchronous. You get <code>202 Accepted</code> straight away.</li>\n<li><strong>Order:</strong> created, then submitted once the person completes their invitation.</li>\n</ul>\n<p>Save the <code>external_id</code>. It is how you look the request up and how status webhooks refer to it.</p>\n<h3 id=\"request-fields\">Request fields</h3>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Type</th>\n<th>Required</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>external_id</code></td>\n<td>string (max 255)</td>\n<td>Recommended</td>\n<td>Your reference for this request. Unique per connection.</td>\n</tr>\n<tr>\n<td><code>consumer.first_name</code></td>\n<td>string</td>\n<td><strong>Yes</strong></td>\n<td></td>\n</tr>\n<tr>\n<td><code>consumer.last_name</code></td>\n<td>string</td>\n<td><strong>Yes</strong></td>\n<td></td>\n</tr>\n<tr>\n<td><code>consumer.email</code></td>\n<td>string</td>\n<td><strong>Yes</strong></td>\n<td>Where the person's invitation goes on email paths.</td>\n</tr>\n<tr>\n<td><code>consumer.phone</code></td>\n<td>string</td>\n<td>No</td>\n<td></td>\n</tr>\n<tr>\n<td><code>consumer.ssn</code></td>\n<td>string</td>\n<td>No</td>\n<td>9 digits. Used to match an existing person.</td>\n</tr>\n<tr>\n<td><code>consumer.date_of_birth</code></td>\n<td>date</td>\n<td>No</td>\n<td><code>YYYY-MM-DD</code></td>\n</tr>\n<tr>\n<td><code>consumer.address</code>, <code>consumer.city</code>, <code>consumer.state</code>, <code>consumer.zip</code></td>\n<td>string</td>\n<td>No</td>\n<td><code>state</code> is the two-letter code.</td>\n</tr>\n<tr>\n<td><code>checklist_id</code></td>\n<td>integer</td>\n<td><strong>One of</strong></td>\n<td>The position to enroll into.</td>\n</tr>\n<tr>\n<td><code>checklist_code</code></td>\n<td>string</td>\n<td><strong>One of</strong></td>\n<td>The position's exact name, as an alternative to <code>checklist_id</code>.</td>\n</tr>\n<tr>\n<td><code>tag_ids</code> / <code>tag_codes</code></td>\n<td>array</td>\n<td>No</td>\n<td>Additional requirement tags, by id or exact name. Unknown tags are ignored.</td>\n</tr>\n<tr>\n<td><code>enrollment_path</code></td>\n<td>string</td>\n<td>No</td>\n<td>How the person is onboarded. See the table below.</td>\n</tr>\n<tr>\n<td><code>create_initial_transaction</code></td>\n<td>boolean</td>\n<td>No</td>\n<td>Overrides whether an order is created. By default one is created on every path except <code>enroll_only</code>.</td>\n</tr>\n<tr>\n<td><code>sync</code></td>\n<td>boolean</td>\n<td>No</td>\n<td>Process before responding. Default <code>false</code>.</td>\n</tr>\n<tr>\n<td><code>user_email</code></td>\n<td>string</td>\n<td>No</td>\n<td>On synchronous requests, the email of the client user to attribute the order to.</td>\n</tr>\n<tr>\n<td><code>metadata</code></td>\n<td>object</td>\n<td>No</td>\n<td>Your own key/value data, stored on the order.</td>\n</tr>\n<tr>\n<td><code>notes</code></td>\n<td>string (max 2000)</td>\n<td>No</td>\n<td>Stored with the request.</td>\n</tr>\n</tbody>\n</table>\n</div><h3 id=\"enrollment-paths\">Enrollment paths</h3>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th><code>enrollment_path</code></th>\n<th>What happens</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>email_onboarding</code></td>\n<td>The person gets an email invitation to fill in their details and give consent. The order is submitted once they finish.</td>\n</tr>\n<tr>\n<td><code>email_consent</code></td>\n<td>Same invitation flow as <code>email_onboarding</code>.</td>\n</tr>\n<tr>\n<td><code>instant</code> / <code>manual_entry</code></td>\n<td>No email to the person. A task is created for the client's team to complete the person's details in PSBI.</td>\n</tr>\n<tr>\n<td><code>enroll_only</code></td>\n<td>Enrolls the person in the position without ordering anything. No email, no order, no status webhooks.</td>\n</tr>\n</tbody>\n</table>\n</div><p>If you leave <code>enrollment_path</code> out, the account's default is used, which is <code>email_onboarding</code> unless PSBI has configured another.</p>\n<h3 id=\"matching-an-existing-person\">Matching an existing person</h3>\n<p>PSBI looks for the person in your account before creating a new record:</p>\n<ol>\n<li><strong>SSN.</strong> An exact match, when <code>consumer.ssn</code> is sent.</li>\n<li><strong>Email plus last name.</strong></li>\n<li>Otherwise a new person is created.</li>\n</ol>\n<p>On a synchronous response, <code>consumer.created</code> tells you which happened.</p>\n","urlObject":{"path":["api","v1","partners","autopilot","enroll"],"host":["https://app.psbi.com"],"query":[],"variable":[]}},"response":[{"id":"dc07e60a-c47f-404a-b846-550ad4efe329","name":"Accepted (async)","originalRequest":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"external_id\": \"HR-2026-001234\",\n  \"consumer\": {\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\",\n    \"email\": \"john.doe@example.com\"\n  },\n  \"checklist_code\": \"Caregiver\"\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/autopilot/enroll"},"status":"Accepted","code":202,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"transaction_id\": 1234,\n  \"external_id\": \"HR-2026-001234\",\n  \"status\": \"received\",\n  \"message\": \"Enrollment request received and queued for processing\",\n  \"received_at\": \"2026-09-15T14:02:11.000000Z\"\n}"}],"_postman_id":"53ed660d-7e96-4567-9313-dd43ab8def33"},{"name":"Enroll Consumer (Full, Sync)","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Enrollment accepted\", function () {","    pm.expect([200, 202]).to.include(pm.response.code);","    pm.expect(pm.response.json().success).to.be.true;","});","","var json = pm.response.json();","if (json.external_id) { pm.collectionVariables.set('last_autopilot_external_id', json.external_id); }","if (json.consumer && json.consumer.id) { pm.collectionVariables.set('last_consumer_id', json.consumer.id); }"]}}],"id":"a3153b79-cd3d-41e5-abd5-3676baafbb6c","request":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key-here","type":"text","description":"<p>Your API key</p>\n"},{"key":"Content-Type","value":"application/json","type":"text"},{"key":"Accept","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"external_id\": \"HR-1789977182\",\n  \"consumer\": {\n    \"first_name\": \"Ethelyn\",\n    \"last_name\": \"Blanda\",\n    \"email\": \"Felix_Upton68@gmail.com\",\n    \"phone\": \"555-123-4567\",\n    \"ssn\": \"123456789\",\n    \"date_of_birth\": \"1990-05-15\",\n    \"address\": \"123 Main Street\",\n    \"city\": \"Austin\",\n    \"state\": \"TX\",\n    \"zip\": \"78701\"\n  },\n  \"checklist_id\": 4,\n  \"tag_codes\": [\"DOT\"],\n  \"enrollment_path\": \"email_onboarding\",\n  \"user_email\": \"hiring.manager@example.com\",\n  \"sync\": true,\n  \"metadata\": {\n    \"department\": \"Nursing\",\n    \"hire_date\": \"2026-10-01\"\n  },\n  \"notes\": \"New hire for ICU\"\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/autopilot/enroll","description":"<p>Enroll with every option set, processed synchronously so the response carries the result.</p>\n<h3 id=\"response-fields-sync\">Response fields (sync)</h3>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>transaction_id</code></td>\n<td>PSBI's id for this API request.</td>\n</tr>\n<tr>\n<td><code>enrollment</code></td>\n<td>The position enrollment: <code>id</code>, <code>checklist_name</code>, <code>status</code>, <code>enrolled_at</code>.</td>\n</tr>\n<tr>\n<td><code>consumer</code></td>\n<td>The person: <code>id</code>, <code>name</code>, <code>email</code>, and <code>created</code> (<code>true</code> if PSBI created a new record, <code>false</code> if it matched an existing one).</td>\n</tr>\n<tr>\n<td><code>transaction</code></td>\n<td>The background check order: <code>id</code> (the <code>consumer_transaction_id</code> on webhooks), <code>status</code>, <code>search_count</code>. Absent when no order was created.</td>\n</tr>\n<tr>\n<td><code>task</code></td>\n<td>The onboarding task created, if any: <code>id</code>, <code>type</code>.</td>\n</tr>\n<tr>\n<td><code>pending_requirements</code></td>\n<td>Present and <code>true</code> when the position still has requirements that aren't satisfied yet.</td>\n</tr>\n</tbody>\n</table>\n</div><p>On email paths the order starts in <code>draft</code> with <code>search_count</code> 0. Searches are added when the person completes their invitation.</p>\n<h3 id=\"synchronous-failures\">Synchronous failures</h3>\n<p>A valid request that cannot be processed returns <code>422</code>. <code>error.code</code> names the reason, for example <code>already_enrolled</code> when the person is already active in this position. The <code>external_id</code> is spent either way, so resend with a new one.</p>\n<h3 id=\"request-fields\">Request fields</h3>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Type</th>\n<th>Required</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>external_id</code></td>\n<td>string (max 255)</td>\n<td>Recommended</td>\n<td>Your reference for this request. Unique per connection.</td>\n</tr>\n<tr>\n<td><code>consumer.first_name</code></td>\n<td>string</td>\n<td><strong>Yes</strong></td>\n<td></td>\n</tr>\n<tr>\n<td><code>consumer.last_name</code></td>\n<td>string</td>\n<td><strong>Yes</strong></td>\n<td></td>\n</tr>\n<tr>\n<td><code>consumer.email</code></td>\n<td>string</td>\n<td><strong>Yes</strong></td>\n<td>Where the person's invitation goes on email paths.</td>\n</tr>\n<tr>\n<td><code>consumer.phone</code></td>\n<td>string</td>\n<td>No</td>\n<td></td>\n</tr>\n<tr>\n<td><code>consumer.ssn</code></td>\n<td>string</td>\n<td>No</td>\n<td>9 digits. Used to match an existing person.</td>\n</tr>\n<tr>\n<td><code>consumer.date_of_birth</code></td>\n<td>date</td>\n<td>No</td>\n<td><code>YYYY-MM-DD</code></td>\n</tr>\n<tr>\n<td><code>consumer.address</code>, <code>consumer.city</code>, <code>consumer.state</code>, <code>consumer.zip</code></td>\n<td>string</td>\n<td>No</td>\n<td><code>state</code> is the two-letter code.</td>\n</tr>\n<tr>\n<td><code>checklist_id</code></td>\n<td>integer</td>\n<td><strong>One of</strong></td>\n<td>The position to enroll into.</td>\n</tr>\n<tr>\n<td><code>checklist_code</code></td>\n<td>string</td>\n<td><strong>One of</strong></td>\n<td>The position's exact name, as an alternative to <code>checklist_id</code>.</td>\n</tr>\n<tr>\n<td><code>tag_ids</code> / <code>tag_codes</code></td>\n<td>array</td>\n<td>No</td>\n<td>Additional requirement tags, by id or exact name. Unknown tags are ignored.</td>\n</tr>\n<tr>\n<td><code>enrollment_path</code></td>\n<td>string</td>\n<td>No</td>\n<td>How the person is onboarded. See the table below.</td>\n</tr>\n<tr>\n<td><code>create_initial_transaction</code></td>\n<td>boolean</td>\n<td>No</td>\n<td>Overrides whether an order is created. By default one is created on every path except <code>enroll_only</code>.</td>\n</tr>\n<tr>\n<td><code>sync</code></td>\n<td>boolean</td>\n<td>No</td>\n<td>Process before responding. Default <code>false</code>.</td>\n</tr>\n<tr>\n<td><code>user_email</code></td>\n<td>string</td>\n<td>No</td>\n<td>On synchronous requests, the email of the client user to attribute the order to.</td>\n</tr>\n<tr>\n<td><code>metadata</code></td>\n<td>object</td>\n<td>No</td>\n<td>Your own key/value data, stored on the order.</td>\n</tr>\n<tr>\n<td><code>notes</code></td>\n<td>string (max 2000)</td>\n<td>No</td>\n<td>Stored with the request.</td>\n</tr>\n</tbody>\n</table>\n</div><h3 id=\"enrollment-paths\">Enrollment paths</h3>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th><code>enrollment_path</code></th>\n<th>What happens</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>email_onboarding</code></td>\n<td>The person gets an email invitation to fill in their details and give consent. The order is submitted once they finish.</td>\n</tr>\n<tr>\n<td><code>email_consent</code></td>\n<td>Same invitation flow as <code>email_onboarding</code>.</td>\n</tr>\n<tr>\n<td><code>instant</code> / <code>manual_entry</code></td>\n<td>No email to the person. A task is created for the client's team to complete the person's details in PSBI.</td>\n</tr>\n<tr>\n<td><code>enroll_only</code></td>\n<td>Enrolls the person in the position without ordering anything. No email, no order, no status webhooks.</td>\n</tr>\n</tbody>\n</table>\n</div><p>If you leave <code>enrollment_path</code> out, the account's default is used, which is <code>email_onboarding</code> unless PSBI has configured another.</p>\n<h3 id=\"matching-an-existing-person\">Matching an existing person</h3>\n<p>PSBI looks for the person in your account before creating a new record:</p>\n<ol>\n<li><strong>SSN.</strong> An exact match, when <code>consumer.ssn</code> is sent.</li>\n<li><strong>Email plus last name.</strong></li>\n<li>Otherwise a new person is created.</li>\n</ol>\n<p>On a synchronous response, <code>consumer.created</code> tells you which happened.</p>\n","urlObject":{"path":["api","v1","partners","autopilot","enroll"],"host":["https://app.psbi.com"],"query":[],"variable":[]}},"response":[{"id":"3c113d0f-d8c1-45e3-8b7b-0fcf459b3d25","name":"Processed (sync)","originalRequest":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"external_id\": \"HR-2026-001234\",\n  \"consumer\": {\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\",\n    \"email\": \"john.doe@example.com\",\n    \"phone\": \"555-123-4567\",\n    \"ssn\": \"123456789\",\n    \"date_of_birth\": \"1990-05-15\",\n    \"address\": \"123 Main Street\",\n    \"city\": \"Austin\",\n    \"state\": \"TX\",\n    \"zip\": \"78701\"\n  },\n  \"checklist_code\": \"Caregiver\",\n  \"sync\": true\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/autopilot/enroll"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"transaction_id\": 1234,\n  \"external_id\": \"HR-2026-001234\",\n  \"status\": \"processed\",\n  \"message\": \"Consumer onboarding task created. Transaction will be created after task completion.\",\n  \"enrollment\": {\n    \"id\": 108,\n    \"checklist_name\": \"Caregiver\",\n    \"status\": \"active\",\n    \"enrolled_at\": \"2026-09-15T14:02:11.000000Z\"\n  },\n  \"consumer\": {\n    \"id\": 54321,\n    \"name\": \"John Doe\",\n    \"email\": \"john.doe@example.com\",\n    \"created\": true\n  },\n  \"transaction\": {\n    \"id\": 98765,\n    \"status\": \"draft\",\n    \"search_count\": 0\n  },\n  \"task\": {\n    \"id\": 9012,\n    \"type\": \"Consumer Onboarding\"\n  },\n  \"pending_requirements\": true\n}"},{"id":"57541f86-7586-4425-90da-efd8deeee5f3","name":"Validation error","originalRequest":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"consumer\": {\n    \"first_name\": \"John\"\n  }\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/autopilot/enroll"},"status":"Unprocessable Entity","code":422,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"VALIDATION_FAILED\",\n    \"message\": \"The provided data is invalid\",\n    \"details\": {\n      \"consumer.last_name\": [\n        \"Consumer last name is required\"\n      ],\n      \"consumer.email\": [\n        \"Consumer email is required\"\n      ],\n      \"checklist_id\": [\n        \"Either checklist_id or checklist_code is required\"\n      ],\n      \"checklist_code\": [\n        \"Either checklist_id or checklist_code is required\"\n      ]\n    }\n  }\n}"},{"id":"884e4fae-b454-4e05-ad07-72e3c2576b67","name":"Checklist not found","originalRequest":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"consumer\": {\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\",\n    \"email\": \"john.doe@example.com\"\n  },\n  \"checklist_code\": \"Invalid Position\"\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/autopilot/enroll"},"status":"Not Found","code":404,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"CHECKLIST_NOT_FOUND\",\n    \"message\": \"Checklist not found or not available\",\n    \"details\": {\n      \"identifier\": \"Invalid Position\"\n    }\n  }\n}"},{"id":"198c67bc-f123-4e9d-ab18-ee590fba9999","name":"Duplicate external_id","originalRequest":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"external_id\": \"HR-2026-001234\",\n  \"consumer\": {\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\",\n    \"email\": \"john.doe@example.com\"\n  },\n  \"checklist_code\": \"Caregiver\"\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/autopilot/enroll"},"status":"Conflict","code":409,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"DUPLICATE_TRANSACTION\",\n    \"message\": \"Transaction with this external ID already exists\",\n    \"details\": {\n      \"external_id\": \"HR-2026-001234\",\n      \"existing_transaction_id\": 1234\n    }\n  }\n}"},{"id":"d71a9bb7-fea7-4812-ad81-a7e16cf355b7","name":"Already enrolled (sync)","originalRequest":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"external_id\": \"HR-2026-001235\",\n  \"consumer\": {\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\",\n    \"email\": \"john.doe@example.com\"\n  },\n  \"checklist_code\": \"Caregiver\",\n  \"sync\": true\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/autopilot/enroll"},"status":"Unprocessable Entity","code":422,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": false,\n  \"transaction_id\": 1235,\n  \"external_id\": \"HR-2026-001235\",\n  \"error\": {\n    \"code\": \"already_enrolled\",\n    \"message\": \"Consumer is already enrolled in Caregiver\"\n  }\n}"}],"_postman_id":"a3153b79-cd3d-41e5-abd5-3676baafbb6c"},{"name":"Enroll Consumer (Enroll Only)","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Enrollment accepted\", function () {","    pm.expect([200, 202]).to.include(pm.response.code);","    pm.expect(pm.response.json().success).to.be.true;","});","","var json = pm.response.json();","if (json.external_id) { pm.collectionVariables.set('last_autopilot_external_id', json.external_id); }","if (json.consumer && json.consumer.id) { pm.collectionVariables.set('last_consumer_id', json.consumer.id); }"]}}],"id":"7dbe619f-3ca7-44d2-aaea-2c8ad8f6e375","request":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key-here","type":"text","description":"<p>Your API key</p>\n"},{"key":"Content-Type","value":"application/json","type":"text"},{"key":"Accept","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"external_id\": \"ENROLL-ONLY-1789977182\",\n  \"consumer\": {\n    \"first_name\": \"Jacynthe\",\n    \"last_name\": \"Schmitt\",\n    \"email\": \"Constantin.Strosin@gmail.com\"\n  },\n  \"checklist_code\": \"Caregiver\",\n  \"enrollment_path\": \"enroll_only\",\n  \"sync\": true\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/autopilot/enroll","description":"<p>Put a person in a position without ordering anything.</p>\n<p>Use this to load existing employees whose screening is already on file, or to start tracking a position before a background check is due.</p>\n<ul>\n<li>An enrollment is created, and the position's requirements show as outstanding (<code>pending_requirements: true</code>).</li>\n<li>No order is created and no email is sent, so <strong>no status webhooks follow</strong>.</li>\n<li>The response has no <code>transaction</code> or <code>task</code>.</li>\n</ul>\n","urlObject":{"path":["api","v1","partners","autopilot","enroll"],"host":["https://app.psbi.com"],"query":[],"variable":[]}},"response":[{"id":"0ce2c55f-dde4-47cc-b1d8-08960b6c8af6","name":"Enrolled without an order","originalRequest":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"external_id\": \"ENROLL-ONLY-12345\",\n  \"consumer\": {\n    \"first_name\": \"Jane\",\n    \"last_name\": \"Smith\",\n    \"email\": \"jane.smith@example.com\"\n  },\n  \"checklist_code\": \"Caregiver\",\n  \"enrollment_path\": \"enroll_only\",\n  \"sync\": true\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/autopilot/enroll"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"transaction_id\": 1236,\n  \"external_id\": \"ENROLL-ONLY-12345\",\n  \"status\": \"processed\",\n  \"message\": \"Successfully enrolled Jane Smith in Caregiver (no initial transaction)\",\n  \"enrollment\": {\n    \"id\": 109,\n    \"checklist_name\": \"Caregiver\",\n    \"status\": \"active\",\n    \"enrolled_at\": \"2026-09-15T14:02:11.000000Z\"\n  },\n  \"consumer\": {\n    \"id\": 54322,\n    \"name\": \"Jane Smith\",\n    \"email\": \"jane.smith@example.com\",\n    \"created\": true\n  },\n  \"pending_requirements\": true\n}"}],"_postman_id":"7dbe619f-3ca7-44d2-aaea-2c8ad8f6e375"}],"id":"496a4984-2ce7-4f9b-be1d-7b4210b18286","description":"<p>Put people into positions.</p>\n","_postman_id":"496a4984-2ce7-4f9b-be1d-7b4210b18286"},{"name":"Transition","item":[{"name":"Transition by Consumer ID","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Transition handled\", function () {","    pm.expect([200, 202, 404]).to.include(pm.response.code);","});","if (pm.response.json().external_id) { pm.collectionVariables.set('last_transition_id', pm.response.json().external_id); }"]}}],"id":"e26b042d-94a3-4ce0-820e-bb4da8dca7dd","request":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key-here","type":"text","description":"<p>Your API key</p>\n"},{"key":"Content-Type","value":"application/json","type":"text"},{"key":"Accept","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"consumer_id\": ,\n  \"new_checklist_code\": \"Mobile Caregiver\",\n  \"external_id\": \"TRANSITION-1789977182\",\n  \"sync\": true\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/autopilot/transition","description":"<p>Move a person to a new position, identified by PSBI's <code>consumer_id</code>.</p>\n<h3 id=\"what-a-transition-does\">What a transition does</h3>\n<ol>\n<li>Finds the person's active position enrollment.</li>\n<li>Compares the new position's requirements with what the person already has.</li>\n<li>Carries over compliance that is still valid (<code>reused_count</code>).</li>\n<li>Orders only what is new (<code>net_new_count</code>). If nothing is new, no order is created and no status webhooks follow.</li>\n<li>Replaces the old enrollment with the new one.</li>\n</ol>\n<h3 id=\"request-fields\">Request fields</h3>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Type</th>\n<th>Required</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>consumer_id</code></td>\n<td>integer</td>\n<td><strong>One of</strong></td>\n<td>PSBI's id for the person.</td>\n</tr>\n<tr>\n<td><code>consumer</code></td>\n<td>object</td>\n<td><strong>One of</strong></td>\n<td>Matching data: <code>ssn</code>, or <code>email</code> <strong>and</strong> <code>last_name</code>.</td>\n</tr>\n<tr>\n<td><code>new_checklist_id</code> / <code>new_checklist_code</code></td>\n<td>integer / string</td>\n<td><strong>One of</strong></td>\n<td>The new position.</td>\n</tr>\n<tr>\n<td><code>external_id</code></td>\n<td>string</td>\n<td>Recommended</td>\n<td>Your reference for this request. Unique per connection.</td>\n</tr>\n<tr>\n<td><code>sync</code></td>\n<td>boolean</td>\n<td>No</td>\n<td>Process before responding.</td>\n</tr>\n<tr>\n<td><code>metadata</code></td>\n<td>object</td>\n<td>No</td>\n<td>Your own key/value data.</td>\n</tr>\n<tr>\n<td><code>notes</code></td>\n<td>string</td>\n<td>No</td>\n<td></td>\n</tr>\n</tbody>\n</table>\n</div><p>Tags are not accepted on transitions.</p>\n<p>Whether or not you send <code>sync</code>, an unknown person (<code>CONSUMER_NOT_FOUND</code>) or position (<code>CHECKLIST_NOT_FOUND</code>) is rejected immediately.</p>\n","urlObject":{"path":["api","v1","partners","autopilot","transition"],"host":["https://app.psbi.com"],"query":[],"variable":[]}},"response":[{"id":"3c0ebe98-2da3-46f6-911c-c9aa0b554d03","name":"Transitioned (sync)","originalRequest":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"consumer_id\": 54321,\n  \"new_checklist_code\": \"Mobile Caregiver\",\n  \"external_id\": \"TRANSITION-001234\",\n  \"sync\": true\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/autopilot/transition"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"transaction_id\": 1237,\n  \"external_id\": \"TRANSITION-001234\",\n  \"status\": \"processed\",\n  \"message\": \"Position transition created with 2 new requirements (3 reused)\",\n  \"enrollment\": {\n    \"id\": 110,\n    \"checklist_name\": \"Mobile Caregiver\",\n    \"status\": \"active\"\n  },\n  \"consumer\": {\n    \"id\": 54321,\n    \"name\": \"John Doe\"\n  },\n  \"analysis\": {\n    \"reused_count\": 3,\n    \"net_new_count\": 2\n  },\n  \"transaction\": {\n    \"id\": 98770,\n    \"status\": \"draft\",\n    \"search_count\": 0\n  }\n}"},{"id":"e255dae9-d283-491f-8dbe-c9278f7e55b0","name":"No active enrollment","originalRequest":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"consumer_id\": 54321,\n  \"new_checklist_code\": \"Mobile Caregiver\",\n  \"sync\": true\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/autopilot/transition"},"status":"Not Found","code":404,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"NO_ACTIVE_ENROLLMENT\",\n    \"message\": \"Consumer has no active AutoPilot enrollment\",\n    \"details\": {\n      \"consumer_id\": 54321\n    }\n  }\n}"}],"_postman_id":"e26b042d-94a3-4ce0-820e-bb4da8dca7dd"},{"name":"Transition by Consumer Matching","id":"65b114dc-f868-4914-a368-441d2761b2f2","request":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key-here","type":"text","description":"<p>Your API key</p>\n"},{"key":"Content-Type","value":"application/json","type":"text"},{"key":"Accept","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"consumer\": {\n    \"ssn\": \"123456789\"\n  },\n  \"new_checklist_id\": 11,\n  \"external_id\": \"TRANSITION-SSN-1789977182\"\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/autopilot/transition","description":"<p>Move a person to a new position when you don't have PSBI's <code>consumer_id</code>.</p>\n<p>Match on SSN (preferred):</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{ \"consumer\": { \"ssn\": \"123456789\" }, \"new_checklist_code\": \"Mobile Caregiver\" }\n</code></pre>\n<p>Or on email and last name together (email alone never matches):</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{ \"consumer\": { \"email\": \"john.doe@example.com\", \"last_name\": \"Doe\" }, \"new_checklist_code\": \"Mobile Caregiver\" }\n</code></pre>\n<h3 id=\"what-a-transition-does\">What a transition does</h3>\n<ol>\n<li>Finds the person's active position enrollment.</li>\n<li>Compares the new position's requirements with what the person already has.</li>\n<li>Carries over compliance that is still valid (<code>reused_count</code>).</li>\n<li>Orders only what is new (<code>net_new_count</code>). If nothing is new, no order is created and no status webhooks follow.</li>\n<li>Replaces the old enrollment with the new one.</li>\n</ol>\n<h3 id=\"request-fields\">Request fields</h3>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Type</th>\n<th>Required</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>consumer_id</code></td>\n<td>integer</td>\n<td><strong>One of</strong></td>\n<td>PSBI's id for the person.</td>\n</tr>\n<tr>\n<td><code>consumer</code></td>\n<td>object</td>\n<td><strong>One of</strong></td>\n<td>Matching data: <code>ssn</code>, or <code>email</code> <strong>and</strong> <code>last_name</code>.</td>\n</tr>\n<tr>\n<td><code>new_checklist_id</code> / <code>new_checklist_code</code></td>\n<td>integer / string</td>\n<td><strong>One of</strong></td>\n<td>The new position.</td>\n</tr>\n<tr>\n<td><code>external_id</code></td>\n<td>string</td>\n<td>Recommended</td>\n<td>Your reference for this request. Unique per connection.</td>\n</tr>\n<tr>\n<td><code>sync</code></td>\n<td>boolean</td>\n<td>No</td>\n<td>Process before responding.</td>\n</tr>\n<tr>\n<td><code>metadata</code></td>\n<td>object</td>\n<td>No</td>\n<td>Your own key/value data.</td>\n</tr>\n<tr>\n<td><code>notes</code></td>\n<td>string</td>\n<td>No</td>\n<td></td>\n</tr>\n</tbody>\n</table>\n</div><p>Tags are not accepted on transitions.</p>\n<p>Whether or not you send <code>sync</code>, an unknown person (<code>CONSUMER_NOT_FOUND</code>) or position (<code>CHECKLIST_NOT_FOUND</code>) is rejected immediately.</p>\n","urlObject":{"path":["api","v1","partners","autopilot","transition"],"host":["https://app.psbi.com"],"query":[],"variable":[]}},"response":[{"id":"cb89c5a5-23a3-495e-9216-478858b77b76","name":"Accepted (async)","originalRequest":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"consumer\": {\n    \"ssn\": \"123456789\"\n  },\n  \"new_checklist_id\": 11,\n  \"external_id\": \"TRANSITION-SSN-001234\"\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/autopilot/transition"},"status":"Accepted","code":202,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"transaction_id\": 1238,\n  \"external_id\": \"TRANSITION-SSN-001234\",\n  \"status\": \"received\",\n  \"message\": \"Transition request received and queued for processing\",\n  \"received_at\": \"2026-09-15T14:02:11.000000Z\"\n}"},{"id":"b067214c-c7b5-4e5a-93a0-1d2f1461b849","name":"Consumer not found","originalRequest":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"consumer\": {\n    \"email\": \"unknown@example.com\",\n    \"last_name\": \"Unknown\"\n  },\n  \"new_checklist_code\": \"Mobile Caregiver\"\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/autopilot/transition"},"status":"Not Found","code":404,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"CONSUMER_NOT_FOUND\",\n    \"message\": \"Consumer not found\",\n    \"details\": {\n      \"identifiers\": [\n        \"email\",\n        \"last_name\"\n      ]\n    }\n  }\n}"}],"_postman_id":"65b114dc-f868-4914-a368-441d2761b2f2"}],"id":"fe9f7baf-93e5-4213-9b00-eeb2443460a5","description":"<p>Move people between positions.</p>\n","_postman_id":"fe9f7baf-93e5-4213-9b00-eeb2443460a5"},{"name":"Status","item":[{"name":"Get Enrollment Status","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Status retrieved\", function () {","    pm.expect([200, 404]).to.include(pm.response.code);","});"]}}],"id":"4c91edbf-d412-434d-9671-e7c6852af771","request":{"method":"GET","header":[{"key":"X-API-Key","value":"your-api-key-here","type":"text","description":"<p>Your API key</p>\n"},{"key":"Accept","value":"application/json","type":"text"}],"url":"https://app.psbi.com/api/v1/partners/autopilot/enrollment/","description":"<p>Look up an enroll or transition request by your <code>external_id</code>.</p>\n<h3 id=\"response\">Response</h3>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>transaction.status</code></td>\n<td>Processing of <strong>your request</strong>: <code>received</code>, then <code>processing</code>, then <code>processed</code> or <code>failed</code>.</td>\n</tr>\n<tr>\n<td><code>transaction.error_message</code></td>\n<td>Why a request <code>failed</code>.</td>\n</tr>\n<tr>\n<td><code>transaction.type</code></td>\n<td><code>autopilot_enroll</code> or <code>autopilot_transition</code>.</td>\n</tr>\n<tr>\n<td><code>enrollment</code></td>\n<td>Present once the request is processed: <code>id</code>, <code>consumer_id</code>, <code>consumer_created</code>, <code>search_count</code>, <code>pending_requirements</code>.</td>\n</tr>\n<tr>\n<td><code>transition_analysis</code></td>\n<td>Transitions only: <code>reused_count</code>, <code>net_new_count</code>.</td>\n</tr>\n<tr>\n<td><code>consumer_transaction</code></td>\n<td>The background check order, if one was created: <code>id</code>, <code>status</code>, <code>submitted_at</code>, <code>consumer</code>.</td>\n</tr>\n</tbody>\n</table>\n</div><p><code>consumer_transaction.status</code> is the order's own status:</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Order status</th>\n<th>Meaning</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>draft</code></td>\n<td>Waiting on the person's invitation or the client's task.</td>\n</tr>\n<tr>\n<td><code>review</code></td>\n<td>Information collected, waiting for the order to be submitted.</td>\n</tr>\n<tr>\n<td><code>processing</code></td>\n<td>Submitted; searches are in progress.</td>\n</tr>\n<tr>\n<td><code>complete</code></td>\n<td>Every search is finished.</td>\n</tr>\n<tr>\n<td><code>cancelled</code></td>\n<td>Cancelled.</td>\n</tr>\n</tbody>\n</table>\n</div><p>A request that was <code>processed</code> without a <code>consumer_transaction</code> either created no order (<code>enroll_only</code>, or a transition with nothing new) or found the person already active in that position.</p>\n<p>You don't need to poll this endpoint once status webhooks are set up. It is useful for confirming a request was processed, and for reconciling after an outage.</p>\n","urlObject":{"path":["api","v1","partners","autopilot","enrollment",""],"host":["https://app.psbi.com"],"query":[],"variable":[]}},"response":[{"id":"a41360b3-64ce-46f5-bf2b-eba3db2ab796","name":"Processed","originalRequest":{"method":"GET","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"url":"https://app.psbi.com/api/v1/partners/autopilot/enrollment/HR-2026-001234"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"transaction\": {\n    \"id\": 1234,\n    \"external_id\": \"HR-2026-001234\",\n    \"type\": \"autopilot_enroll\",\n    \"status\": \"processed\",\n    \"received_at\": \"2026-09-15T14:02:11.000000Z\",\n    \"processed_at\": \"2026-09-15T14:02:43.000000Z\",\n    \"error_message\": null\n  },\n  \"enrollment\": {\n    \"id\": 108,\n    \"consumer_id\": 54321,\n    \"consumer_created\": true,\n    \"search_count\": 0,\n    \"pending_requirements\": true\n  },\n  \"consumer_transaction\": {\n    \"id\": 98765,\n    \"status\": \"draft\",\n    \"submitted_at\": null,\n    \"consumer\": {\n      \"id\": 54321,\n      \"name\": \"John Doe\"\n    }\n  }\n}"},{"id":"58e9d62e-3b0e-40fe-a506-7947e3796ac4","name":"Failed","originalRequest":{"method":"GET","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"url":"https://app.psbi.com/api/v1/partners/autopilot/enrollment/HR-2026-001299"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"transaction\": {\n    \"id\": 1299,\n    \"external_id\": \"HR-2026-001299\",\n    \"type\": \"autopilot_enroll\",\n    \"status\": \"failed\",\n    \"received_at\": \"2026-09-15T14:02:11.000000Z\",\n    \"processed_at\": \"2026-09-15T14:02:43.000000Z\",\n    \"error_message\": \"Enrollment created but transaction failed: No requirements found for this checklist\"\n  }\n}"},{"id":"5a114ba5-e475-4eaa-bf9e-d3669e25cc08","name":"Not found","originalRequest":{"method":"GET","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"url":"https://app.psbi.com/api/v1/partners/autopilot/enrollment/UNKNOWN-ID"},"status":"Not Found","code":404,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"TRANSACTION_NOT_FOUND\",\n    \"message\": \"Transaction not found\",\n    \"details\": {\n      \"external_id\": \"UNKNOWN-ID\"\n    }\n  }\n}"}],"_postman_id":"4c91edbf-d412-434d-9671-e7c6852af771"}],"id":"3dfd4786-b9a5-4435-b5d9-70c1d1c6e631","description":"<p>Check the outcome of enroll and transition requests.</p>\n","_postman_id":"3dfd4786-b9a5-4435-b5d9-70c1d1c6e631"}],"id":"9bb3eb6c-1cdf-4187-9270-c32b8fccdfdb","description":"<h1 id=\"autopilot-api\">AutoPilot API</h1>\n<p>AutoPilot manages a person's compliance for a <strong>position</strong>. You say which position someone holds, and PSBI orders what that position requires, tracks when each requirement expires, and handles changes when the person moves to another position.</p>\n<h2 id=\"concepts\">Concepts</h2>\n<ul>\n<li><strong>Checklist (position).</strong> A named set of requirements, such as \"Caregiver\" or \"Driver\". Your PSBI implementation contact sets these up and gives you their ids or exact names.</li>\n<li><strong>Tags.</strong> Extra requirements layered onto a position, such as \"DOT\".</li>\n<li><strong>Enrollment.</strong> A person's membership in a position.</li>\n<li><strong>Order.</strong> The background check PSBI places to satisfy the requirements. This is what status webhooks report on.</li>\n</ul>\n<h2 id=\"endpoints\">Endpoints</h2>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Endpoint</th>\n<th>Use</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>POST /api/v1/partners/autopilot/enroll</code></td>\n<td>Put a person in a position.</td>\n</tr>\n<tr>\n<td><code>POST /api/v1/partners/autopilot/transition</code></td>\n<td>Move a person to a different position.</td>\n</tr>\n<tr>\n<td><code>GET /api/v1/partners/autopilot/enrollment/{external_id}</code></td>\n<td>Check a request's outcome.</td>\n</tr>\n</tbody>\n</table>\n</div><h2 id=\"enrolling-someone-who-is-already-enrolled\">Enrolling someone who is already enrolled</h2>\n<ul>\n<li><strong>Asynchronous requests.</strong> If the person is already active in <strong>the same</strong> position, the request is marked <code>processed</code> with no new order. If they are active in <strong>a different</strong> position, the enroll is handled as a transition to the new one.</li>\n<li><strong>Synchronous requests.</strong> The same position is rejected with <code>422 already_enrolled</code>.</li>\n</ul>\n","_postman_id":"9bb3eb6c-1cdf-4187-9270-c32b8fccdfdb"},{"name":"Partner Gateway","item":[{"name":"Create Consumer Transaction","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Transaction accepted\", function () {","    pm.expect([200, 202]).to.include(pm.response.code);","    pm.expect(pm.response.json().success).to.be.true;","});","if (pm.response.json().external_id) { pm.collectionVariables.set('last_gateway_external_id', pm.response.json().external_id); }"]}}],"id":"f03768f4-3503-4daa-a45f-8d75bbb93fa1","request":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key-here","type":"text","description":"<p>Your API key</p>\n"},{"key":"Content-Type","value":"application/json","type":"text"},{"key":"Accept","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"external_id\": \"CONSUMER_1789977182\",\n  \"transaction_type\": \"consumer\",\n  \"consumer\": {\n    \"first_name\": \"Tyree\",\n    \"last_name\": \"Gleason\",\n    \"email\": \"Aylin.Walsh@hotmail.com\",\n    \"phone\": \"555-123-4567\",\n    \"date_of_birth\": \"1990-01-15\",\n    \"address\": \"456 Oak Avenue\",\n    \"city\": \"Austin\",\n    \"state\": \"TX\",\n    \"zip\": \"78701\"\n  },\n  \"products\": [\"Statewide Criminal\", \"Sex Offender Registry\"],\n  \"metadata\": {\n    \"employee_id\": \"E-10442\",\n    \"notes\": \"Pre-hire screening\"\n  }\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/transactions","description":"<p>Order background check products for a person, who receives an email invitation to complete their details and give consent.</p>\n<p><code>transaction_type: \"consumer\"</code> is the default. Processing is asynchronous unless you send <code>\"sync\": true</code>.</p>\n<p>Status webhooks work the same for Gateway orders as for AutoPilot orders.</p>\n<h3 id=\"request-fields\">Request fields</h3>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Type</th>\n<th>Required</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>external_id</code></td>\n<td>string (max 255)</td>\n<td>Recommended</td>\n<td>Your reference. Unique per connection; reusing it returns <code>409</code>.</td>\n</tr>\n<tr>\n<td><code>transaction_type</code></td>\n<td>string</td>\n<td>No</td>\n<td><code>consumer</code> (default) or <code>client</code>.</td>\n</tr>\n<tr>\n<td><code>consumer.first_name</code>, <code>consumer.last_name</code></td>\n<td>string</td>\n<td><strong>Yes</strong></td>\n<td></td>\n</tr>\n<tr>\n<td><code>consumer.email</code></td>\n<td>string</td>\n<td><strong>Yes</strong></td>\n<td></td>\n</tr>\n<tr>\n<td><code>consumer.phone</code>, <code>consumer.ssn</code>, <code>consumer.date_of_birth</code></td>\n<td>string / date</td>\n<td>No</td>\n<td></td>\n</tr>\n<tr>\n<td><code>consumer.address</code>, <code>consumer.city</code>, <code>consumer.state</code>, <code>consumer.zip</code></td>\n<td>string</td>\n<td>No</td>\n<td></td>\n</tr>\n<tr>\n<td><code>products</code></td>\n<td>array</td>\n<td><strong>Yes</strong></td>\n<td>At least one product. See below.</td>\n</tr>\n<tr>\n<td><code>metadata</code></td>\n<td>object</td>\n<td>No</td>\n<td>Your own key/value data, such as <code>employee_id</code> or <code>notes</code>.</td>\n</tr>\n<tr>\n<td><code>sync</code></td>\n<td>boolean</td>\n<td>No</td>\n<td>Process before responding. Default <code>false</code>.</td>\n</tr>\n</tbody>\n</table>\n</div><h3 id=\"products\">Products</h3>\n<p>Each entry in <code>products</code> can be any of these:</p>\n<ul>\n<li>a product name: <code>\"County Criminal\"</code></li>\n<li>a PSBI product id: <code>12</code></li>\n<li>an object: <code>{ \"product_id\": 12 }</code>, <code>{ \"product_name\": \"County Criminal\" }</code>, <code>{ \"order_template_id\": 3 }</code> or <code>{ \"order_template_name\": \"Standard Package\" }</code></li>\n</ul>\n<p>Names are matched against your account's products, then its order templates (packages). If nothing matches, your account's default products are ordered rather than the request failing. Confirm the exact product and package names with your implementation contact before you go live.</p>\n","urlObject":{"path":["api","v1","partners","transactions"],"host":["https://app.psbi.com"],"query":[],"variable":[]}},"response":[{"id":"ff0fd735-6f32-4df9-af83-6a97bca68365","name":"Accepted (async)","originalRequest":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"external_id\": \"CONSUMER_12345\",\n  \"consumer\": {\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\",\n    \"email\": \"john.doe@example.com\"\n  },\n  \"products\": [\n    \"Statewide Criminal\"\n  ]\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/transactions"},"status":"Accepted","code":202,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"transaction_id\": 5114,\n  \"external_id\": \"CONSUMER_12345\",\n  \"status\": \"received\",\n  \"message\": \"Transaction received and queued for processing\",\n  \"received_at\": \"2026-09-15T14:02:11.000000Z\",\n  \"estimated_completion\": \"2026-09-18T14:02:11.000000Z\",\n  \"tracking_url\": null,\n  \"consumer\": {\n    \"id\": null,\n    \"name\": \"John Doe\",\n    \"email\": \"john.doe@example.com\"\n  }\n}"},{"id":"babe810b-ab5b-4a06-b473-83ed1619ad90","name":"Processed (sync)","originalRequest":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"external_id\": \"CONSUMER_12345\",\n  \"consumer\": {\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\",\n    \"email\": \"john.doe@example.com\"\n  },\n  \"products\": [\n    \"Statewide Criminal\"\n  ],\n  \"sync\": true\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/transactions"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"transaction_id\": 5114,\n  \"external_id\": \"CONSUMER_12345\",\n  \"status\": \"processed\",\n  \"message\": \"Transaction processed successfully\",\n  \"received_at\": \"2026-09-15T14:02:11.000000Z\",\n  \"estimated_completion\": \"2026-09-18T14:02:11.000000Z\",\n  \"tracking_url\": null,\n  \"consumer\": {\n    \"id\": 54321,\n    \"name\": \"John Doe\",\n    \"email\": \"john.doe@example.com\"\n  }\n}"},{"id":"0820aba7-c145-4b9b-94b9-cc7cc905707f","name":"Validation error","originalRequest":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"consumer\": {\n    \"first_name\": \"John\"\n  }\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/transactions"},"status":"Unprocessable Entity","code":422,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"VALIDATION_FAILED\",\n    \"message\": \"The provided data is invalid\",\n    \"details\": {\n      \"consumer.last_name\": [\n        \"Consumer last name is required\"\n      ],\n      \"consumer.email\": [\n        \"Consumer email is required\"\n      ],\n      \"products\": [\n        \"At least one product or service is required\"\n      ]\n    }\n  }\n}"},{"id":"38bbd9a7-d987-45e2-bec1-6398f099d87f","name":"Duplicate external_id","originalRequest":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"external_id\": \"CONSUMER_12345\",\n  \"consumer\": {\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\",\n    \"email\": \"john.doe@example.com\"\n  },\n  \"products\": [\n    \"Statewide Criminal\"\n  ]\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/transactions"},"status":"Conflict","code":409,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"DUPLICATE_TRANSACTION\",\n    \"message\": \"This event has already been processed\",\n    \"details\": {\n      \"external_id\": \"CONSUMER_12345\",\n      \"original_transaction_id\": 5114,\n      \"original_status\": \"processed\"\n    }\n  }\n}"}],"_postman_id":"f03768f4-3503-4daa-a45f-8d75bbb93fa1"},{"name":"Create Client Transaction","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Transaction accepted\", function () {","    pm.expect([200, 202]).to.include(pm.response.code);","});","if (pm.response.json().external_id) { pm.collectionVariables.set('last_gateway_external_id', pm.response.json().external_id); }"]}}],"id":"c8dbbc52-ac52-46f3-a1d0-d2e0bf8e1189","request":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key-here","type":"text","description":"<p>Your API key</p>\n"},{"key":"Content-Type","value":"application/json","type":"text"},{"key":"Accept","value":"application/json","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"external_id\": \"CLIENT_1789977182\",\n  \"transaction_type\": \"client\",\n  \"consumer\": {\n    \"first_name\": \"Jeffery\",\n    \"last_name\": \"Padberg\",\n    \"email\": \"Meggie_Erdman33@gmail.com\",\n    \"ssn\": \"123456789\",\n    \"date_of_birth\": \"1985-03-22\",\n    \"state\": \"TX\"\n  },\n  \"products\": [\"County Criminal\", \"National Criminal\"]\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/transactions","description":"<p>Order background check products without emailing the person. A task is created for the client's team to complete the person's details in PSBI.</p>\n<p>Use this when your client collects the information and consent themselves.</p>\n<p>The body is the same as <strong>Create Consumer Transaction</strong>, with <code>transaction_type</code> set to <code>\"client\"</code>.</p>\n","urlObject":{"path":["api","v1","partners","transactions"],"host":["https://app.psbi.com"],"query":[],"variable":[]}},"response":[{"id":"583e8659-4aea-43d4-82c7-9895ab3d159d","name":"Accepted (async)","originalRequest":{"method":"POST","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"body":{"mode":"raw","raw":"{\n  \"external_id\": \"CLIENT_67890\",\n  \"transaction_type\": \"client\",\n  \"consumer\": {\n    \"first_name\": \"Jane\",\n    \"last_name\": \"Smith\",\n    \"email\": \"jane.smith@example.com\"\n  },\n  \"products\": [\n    \"County Criminal\",\n    \"National Criminal\"\n  ]\n}","options":{"raw":{"language":"json"}}},"url":"https://app.psbi.com/api/v1/partners/transactions"},"status":"Accepted","code":202,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"transaction_id\": 5115,\n  \"external_id\": \"CLIENT_67890\",\n  \"status\": \"received\",\n  \"message\": \"Transaction received and queued for processing\",\n  \"received_at\": \"2026-09-15T14:02:11.000000Z\",\n  \"estimated_completion\": \"2026-09-18T14:02:11.000000Z\",\n  \"tracking_url\": null,\n  \"consumer\": {\n    \"id\": null,\n    \"name\": \"Jane Smith\",\n    \"email\": \"jane.smith@example.com\"\n  }\n}"}],"_postman_id":"c8dbbc52-ac52-46f3-a1d0-d2e0bf8e1189"},{"name":"Get Transaction Status","event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Status retrieved\", function () {","    pm.expect([200, 404]).to.include(pm.response.code);","});"]}}],"id":"684756fa-9000-4ddd-8c55-8957e9096fbb","request":{"method":"GET","header":[{"key":"X-API-Key","value":"your-api-key-here","type":"text","description":"<p>Your API key</p>\n"},{"key":"Accept","value":"application/json","type":"text"}],"url":"https://app.psbi.com/api/v1/partners/transactions/","description":"<p>Look up any request on your connection by <code>external_id</code>. This covers Gateway orders and AutoPilot enrollments.</p>\n<h3 id=\"response\">Response</h3>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>transaction.id</code></td>\n<td>PSBI's request id, prefixed <code>PTX-</code>.</td>\n</tr>\n<tr>\n<td><code>transaction.status</code></td>\n<td>Processing of your request: <code>received</code>, <code>processing</code>, <code>processed</code> or <code>failed</code>.</td>\n</tr>\n<tr>\n<td><code>transaction.error_message</code></td>\n<td>Why a request <code>failed</code>.</td>\n</tr>\n<tr>\n<td><code>transaction.consumer_transaction</code></td>\n<td>The order, once created: <code>id</code>, <code>status</code> (<code>draft</code>, <code>review</code>, <code>processing</code>, <code>complete</code>, <code>cancelled</code>), <code>estimated_total</code>, <code>submitted_at</code>, <code>consumer</code>.</td>\n</tr>\n<tr>\n<td><code>transaction.status_updates</code></td>\n<td>The five most recent status webhooks PSBI attempted for this request: <code>event</code>, <code>status</code> (<code>sent</code>, <code>failed</code>, <code>retrying</code> or <code>pending</code>), <code>timestamp</code>, <code>delivered_at</code>. Useful for confirming your endpoint received them.</td>\n</tr>\n</tbody>\n</table>\n</div>","urlObject":{"path":["api","v1","partners","transactions",""],"host":["https://app.psbi.com"],"query":[],"variable":[]}},"response":[{"id":"fba75b9e-979d-4bad-a59c-565ddb825965","name":"In progress","originalRequest":{"method":"GET","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"url":"https://app.psbi.com/api/v1/partners/transactions/CONSUMER_12345"},"status":"OK","code":200,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": true,\n  \"transaction\": {\n    \"id\": \"PTX-5114\",\n    \"external_id\": \"CONSUMER_12345\",\n    \"status\": \"processed\",\n    \"transaction_type\": \"consumer\",\n    \"received_at\": \"2026-09-15T14:02:11.000000Z\",\n    \"processed_at\": \"2026-09-15T14:02:43.000000Z\",\n    \"error_message\": null,\n    \"consumer_transaction\": {\n      \"id\": 98765,\n      \"status\": \"processing\",\n      \"estimated_total\": \"45.00\",\n      \"submitted_at\": \"2026-09-16T09:12:40.000000Z\",\n      \"consumer\": {\n        \"id\": 54321,\n        \"name\": \"John Doe\"\n      }\n    },\n    \"status_updates\": [\n      {\n        \"event\": \"consumer.transaction.submitted\",\n        \"status\": \"sent\",\n        \"timestamp\": \"2026-09-16T09:12:41.000000Z\",\n        \"delivered_at\": \"2026-09-16T09:12:41.000000Z\"\n      },\n      {\n        \"event\": \"consumer.transaction.review\",\n        \"status\": \"sent\",\n        \"timestamp\": \"2026-09-16T09:10:02.000000Z\",\n        \"delivered_at\": \"2026-09-16T09:10:02.000000Z\"\n      }\n    ]\n  }\n}"},{"id":"e02d9859-7971-4fb5-a960-72ab7aa4a267","name":"Not found","originalRequest":{"method":"GET","header":[{"key":"X-API-Key","value":"your-api-key","type":"text"}],"url":"https://app.psbi.com/api/v1/partners/transactions/UNKNOWN_ID"},"status":"Not Found","code":404,"_postman_previewlanguage":"json","header":[{"key":"Content-Type","value":"application/json"}],"cookie":[],"responseTime":null,"body":"{\n  \"success\": false,\n  \"error\": {\n    \"code\": \"TRANSACTION_NOT_FOUND\",\n    \"message\": \"Transaction not found\"\n  }\n}"}],"_postman_id":"684756fa-9000-4ddd-8c55-8957e9096fbb"}],"id":"73d74ad4-20ea-4a78-b508-cbc92c178fb1","description":"<h1 id=\"partner-gateway\">Partner Gateway</h1>\n<p>Order specific background check products for a person.</p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th><code>transaction_type</code></th>\n<th>Who completes the person's information</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>consumer</code> (default)</td>\n<td>The person, through an email invitation.</td>\n</tr>\n<tr>\n<td><code>client</code></td>\n<td>The client's team, through a task in PSBI.</td>\n</tr>\n</tbody>\n</table>\n</div><h2 id=\"flow\">Flow</h2>\n<ol>\n<li><strong>Create</strong> the transaction with your <code>external_id</code>.</li>\n<li>The person or the client's team <strong>completes</strong> the information. The order then moves to review and is submitted.</li>\n<li><strong>Receive status webhooks</strong>, or poll <strong>Get Transaction Status</strong>.</li>\n<li>When the order is <strong>complete</strong>, the report-ready webhook carries a link to download the report.</li>\n</ol>\n<p>To enroll people into positions and keep them compliant over time, use the <strong>AutoPilot API</strong> instead.</p>\n","_postman_id":"73d74ad4-20ea-4a78-b508-cbc92c178fb1"},{"name":"Status Webhooks","item":[{"name":"Review","id":"1ad4722a-77f9-4387-bd1f-5e27c7c949c2","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"},{"key":"User-Agent","value":"P51-Partner-Gateway/1.0","type":"text"},{"key":"X-P51-Event","value":"consumer.transaction.review","type":"text"},{"key":"X-P51-Timestamp","value":"2026-09-15T18:22:31.000000Z","type":"text"},{"key":"X-P51-Signature","value":"3f1c9a0e7b…","type":"text","description":"<p>Hex HMAC-SHA256 of the raw body with your signing secret</p>\n"}],"body":{"mode":"raw","raw":"{\n  \"event\": \"consumer.transaction.review\",\n  \"status\": \"review\",\n  \"external_id\": \"HR-2026-001234\",\n  \"consumer_transaction_id\": \"98765\",\n  \"consumer\": {\n    \"id\": \"54321\",\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\"\n  },\n  \"portal_url\": \"https://app.psbi.com/client/consumers/54321/transactions/98765/review\",\n  \"occurred_at\": \"2026-09-15T18:22:31Z\"\n}","options":{"raw":{"language":"json"}}},"url":"https://your-app.example.com/psbi/webhooks","description":"<p><strong>Sent by PSBI to your endpoint.</strong></p>\n<p>The person's information is collected and the order is waiting to be submitted, either by the client's team at <code>portal_url</code> or automatically. It is not a results review; nothing has been searched yet.</p>\n<p>Suggested handling: mark the record as waiting to be submitted.</p>\n","urlObject":{"host":["https://your-app.example.com/psbi/webhooks"],"query":[],"variable":[]}},"response":[],"_postman_id":"1ad4722a-77f9-4387-bd1f-5e27c7c949c2"},{"name":"In Progress","id":"0000fc28-31ef-45ee-a9eb-8eb8076a0ce2","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"},{"key":"User-Agent","value":"P51-Partner-Gateway/1.0","type":"text"},{"key":"X-P51-Event","value":"consumer.transaction.submitted","type":"text"},{"key":"X-P51-Timestamp","value":"2026-09-15T18:22:31.000000Z","type":"text"},{"key":"X-P51-Signature","value":"3f1c9a0e7b…","type":"text","description":"<p>Hex HMAC-SHA256 of the raw body with your signing secret</p>\n"}],"body":{"mode":"raw","raw":"{\n  \"event\": \"consumer.transaction.submitted\",\n  \"status\": \"in_progress\",\n  \"external_id\": \"HR-2026-001234\",\n  \"consumer_transaction_id\": \"98765\",\n  \"consumer\": {\n    \"id\": \"54321\",\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\"\n  },\n  \"portal_url\": \"https://app.psbi.com/client/autopilot/consumers/54321\",\n  \"occurred_at\": \"2026-09-15T18:22:31Z\"\n}","options":{"raw":{"language":"json"}}},"url":"https://your-app.example.com/psbi/webhooks","description":"<p><strong>Sent by PSBI to your endpoint.</strong></p>\n<p>The order is submitted and searches are running. Turnaround depends on the searches; county courts can take several business days.</p>\n<p>Suggested handling: mark the record as screening in progress.</p>\n","urlObject":{"host":["https://your-app.example.com/psbi/webhooks"],"query":[],"variable":[]}},"response":[],"_postman_id":"0000fc28-31ef-45ee-a9eb-8eb8076a0ce2"},{"name":"Complete","id":"b4f8b2dc-dfa8-423a-8770-3260124be010","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"},{"key":"User-Agent","value":"P51-Partner-Gateway/1.0","type":"text"},{"key":"X-P51-Event","value":"consumer.transaction.completed","type":"text"},{"key":"X-P51-Timestamp","value":"2026-09-15T18:22:31.000000Z","type":"text"},{"key":"X-P51-Signature","value":"3f1c9a0e7b…","type":"text","description":"<p>Hex HMAC-SHA256 of the raw body with your signing secret</p>\n"}],"body":{"mode":"raw","raw":"{\n  \"event\": \"consumer.transaction.completed\",\n  \"status\": \"complete\",\n  \"external_id\": \"HR-2026-001234\",\n  \"consumer_transaction_id\": \"98765\",\n  \"consumer\": {\n    \"id\": \"54321\",\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\"\n  },\n  \"portal_url\": \"https://app.psbi.com/client/autopilot/consumers/54321\",\n  \"occurred_at\": \"2026-09-15T18:22:31Z\",\n  \"result\": \"needs_review\"\n}","options":{"raw":{"language":"json"}}},"url":"https://your-app.example.com/psbi/webhooks","description":"<p><strong>Sent by PSBI to your endpoint.</strong></p>\n<p>Every search is finished.</p>\n<ul>\n<li><code>result: \"clear\"</code>: nothing needs review.</li>\n<li><code>result: \"needs_review\"</code>: at least one search returned something to review. The client adjudicates in PSBI at <code>portal_url</code>. Do not treat <code>needs_review</code> as a failed or adverse decision.</li>\n</ul>\n<p>The report PDF follows in <strong>Report Ready</strong>.</p>\n","urlObject":{"host":["https://your-app.example.com/psbi/webhooks"],"query":[],"variable":[]}},"response":[],"_postman_id":"b4f8b2dc-dfa8-423a-8770-3260124be010"},{"name":"Report Ready","id":"e11c703c-778e-4a89-8ca0-cba28996e8a1","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json","type":"text"},{"key":"User-Agent","value":"P51-Partner-Gateway/1.0","type":"text"},{"key":"X-P51-Event","value":"consumer.transaction.report_generated","type":"text"},{"key":"X-P51-Timestamp","value":"2026-09-15T18:22:31.000000Z","type":"text"},{"key":"X-P51-Signature","value":"3f1c9a0e7b…","type":"text","description":"<p>Hex HMAC-SHA256 of the raw body with your signing secret</p>\n"}],"body":{"mode":"raw","raw":"{\n  \"event\": \"consumer.transaction.report_generated\",\n  \"status\": \"complete\",\n  \"external_id\": \"HR-2026-001234\",\n  \"consumer_transaction_id\": \"98765\",\n  \"consumer\": {\n    \"id\": \"54321\",\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\"\n  },\n  \"portal_url\": \"https://app.psbi.com/client/autopilot/consumers/54321\",\n  \"occurred_at\": \"2026-09-15T18:22:31Z\",\n  \"result\": \"needs_review\",\n  \"report_url\": \"https://<psbi-report-storage>/reports/98765.pdf\"\n}","options":{"raw":{"language":"json"}}},"url":"https://your-app.example.com/psbi/webhooks","description":"<p><strong>Sent by PSBI to your endpoint.</strong></p>\n<p>The report PDF is generated. Download it from <code>report_url</code> straight away and store it with the same care as any consumer report. <code>result</code> repeats the outcome from <strong>Complete</strong>.</p>\n","urlObject":{"host":["https://your-app.example.com/psbi/webhooks"],"query":[],"variable":[]}},"response":[],"_postman_id":"e11c703c-778e-4a89-8ca0-cba28996e8a1"}],"id":"5e523db0-67e9-4371-9d93-fe4eb273a55a","description":"<h1 id=\"status-webhooks\">Status Webhooks</h1>\n<p>PSBI posts to your HTTPS endpoint as an order moves through screening. These are <strong>requests PSBI sends to you</strong>, not endpoints you call.</p>\n<h2 id=\"turning-them-on\">Turning them on</h2>\n<p>Send your implementation contact:</p>\n<ul>\n<li>the HTTPS URL that should receive webhooks.</li>\n</ul>\n<p>PSBI registers it on your connection and gives you a <strong>signing secret</strong>. Webhooks then cover every order created through your API key, from both AutoPilot enrollments and Gateway transactions.</p>\n<h2 id=\"events\">Events</h2>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th><code>event</code></th>\n<th><code>status</code></th>\n<th>When</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>consumer.transaction.review</code></td>\n<td><code>review</code></td>\n<td>The person's information is collected and the order is waiting to be submitted. <strong>This is not a results review.</strong> On accounts that submit automatically, <code>in_progress</code> follows within seconds.</td>\n</tr>\n<tr>\n<td><code>consumer.transaction.submitted</code></td>\n<td><code>in_progress</code></td>\n<td>The order is submitted and searches are running.</td>\n</tr>\n<tr>\n<td><code>consumer.transaction.completed</code></td>\n<td><code>complete</code></td>\n<td>Every search is finished. <code>result</code> says whether anything needs review.</td>\n</tr>\n<tr>\n<td><code>consumer.transaction.report_generated</code></td>\n<td><code>complete</code></td>\n<td>The report PDF is ready. <code>report_url</code> links to it. This usually follows <code>completed</code> within minutes.</td>\n</tr>\n</tbody>\n</table>\n</div><h2 id=\"body\">Body</h2>\n<p>Every event has the same shape. <strong>All values are strings.</strong></p>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Field</th>\n<th>Description</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>event</code></td>\n<td>The event name from the table above.</td>\n</tr>\n<tr>\n<td><code>status</code></td>\n<td><code>review</code>, <code>in_progress</code> or <code>complete</code>.</td>\n</tr>\n<tr>\n<td><code>external_id</code></td>\n<td>The <code>external_id</code> you sent when you enrolled or ordered.</td>\n</tr>\n<tr>\n<td><code>consumer_transaction_id</code></td>\n<td>PSBI's order id.</td>\n</tr>\n<tr>\n<td><code>consumer.id</code>, <code>consumer.first_name</code>, <code>consumer.last_name</code></td>\n<td>The person.</td>\n</tr>\n<tr>\n<td><code>portal_url</code></td>\n<td>Where a client user views this in PSBI (a PSBI login is required). On <code>review</code> it opens the order's submit page.</td>\n</tr>\n<tr>\n<td><code>occurred_at</code></td>\n<td>When PSBI generated the webhook (UTC).</td>\n</tr>\n<tr>\n<td><code>result</code></td>\n<td><code>completed</code> and <code>report_generated</code> only. <code>clear</code> means no search needs review. <code>needs_review</code> means at least one search returned something to review, such as a record found or a verification that could not be confirmed. Adjudication happens in PSBI.</td>\n</tr>\n<tr>\n<td><code>report_url</code></td>\n<td><code>report_generated</code> only. A download link for the report PDF. It is a consumer report, so fetch and store it securely straight away rather than keeping the link.</td>\n</tr>\n</tbody>\n</table>\n</div><pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-json\">{\n  \"event\": \"consumer.transaction.completed\",\n  \"status\": \"complete\",\n  \"external_id\": \"HR-2026-001234\",\n  \"consumer_transaction_id\": \"98765\",\n  \"consumer\": {\n    \"id\": \"54321\",\n    \"first_name\": \"John\",\n    \"last_name\": \"Doe\"\n  },\n  \"portal_url\": \"https://app.psbi.com/client/autopilot/consumers/54321\",\n  \"occurred_at\": \"2026-09-15T18:22:31Z\",\n  \"result\": \"needs_review\"\n}\n</code></pre>\n<h2 id=\"headers\">Headers</h2>\n<div class=\"click-to-expand-wrapper is-table-wrapper\"><table>\n<thead>\n<tr>\n<th>Header</th>\n<th>Value</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>Content-Type</code></td>\n<td><code>application/json</code></td>\n</tr>\n<tr>\n<td><code>User-Agent</code></td>\n<td><code>P51-Partner-Gateway/1.0</code></td>\n</tr>\n<tr>\n<td><code>X-P51-Event</code></td>\n<td>The event name.</td>\n</tr>\n<tr>\n<td><code>X-P51-Timestamp</code></td>\n<td>When the request was sent (ISO 8601).</td>\n</tr>\n<tr>\n<td><code>X-P51-Signature</code></td>\n<td>Lowercase hex HMAC-SHA256 of the <strong>raw request body</strong>, keyed with your signing secret.</td>\n</tr>\n</tbody>\n</table>\n</div><h2 id=\"verifying-the-signature\">Verifying the signature</h2>\n<p>Compute the HMAC over the exact bytes you received, before parsing the JSON, and compare it in constant time. Reject anything that does not match.</p>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-js\">const crypto = require('crypto');\n\n// Express: app.post('/psbi/webhooks', express.raw({ type: 'application/json' }), handler)\nfunction isFromPsbi(rawBody, signatureHeader, secret) {\n  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');\n  const received = String(signatureHeader || '').replace(/^sha256=/, '');\n  return received.length === expected.length &amp;&amp;\n    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));\n}\n</code></pre>\n<pre class=\"click-to-expand-wrapper is-snippet-wrapper\"><code class=\"language-php\">$raw = file_get_contents('php://input');\n$expected = hash_hmac('sha256', $raw, $secret);\n\nif (! hash_equals($expected, $_SERVER['HTTP_X_P51_SIGNATURE'] ?? '')) {\n    http_response_code(401);\n    exit;\n}\n</code></pre>\n<h2 id=\"responding\">Responding</h2>\n<ul>\n<li>Return any <strong>2xx</strong> within <strong>30 seconds</strong>. Acknowledge first and do slow work afterwards.</li>\n<li>A timeout or any non-2xx counts as a failed delivery. PSBI tries <strong>3 times in total</strong>, about a minute apart.</li>\n</ul>\n<h2 id=\"designing-your-receiver\">Designing your receiver</h2>\n<ul>\n<li><strong>Duplicates happen.</strong> A retry after a slow response can deliver the same event twice. Deduplicate on <code>external_id</code> + <code>event</code> + <code>occurred_at</code>, or make your updates idempotent.</li>\n<li><strong>Order is not guaranteed.</strong> Don't move a record backwards, for example from <code>complete</code> to <code>in_progress</code>.</li>\n<li><strong>Not every order sends every event.</strong> <code>review</code> can be followed immediately by <code>in_progress</code>. An order that is reopened and completed again sends <code>completed</code> and <code>report_generated</code> again; treat the latest one as current.</li>\n<li><strong>What sends no webhooks:</strong><ul>\n<li><code>enroll_only</code> enrollments</li>\n<li>a transition with no new requirements</li>\n<li>an enroll that found the person already active in that position</li>\n<li>orders PSBI creates later on its own, such as renewals</li>\n</ul>\n</li>\n<li><strong>Missed one?</strong> <strong>Get Enrollment Status</strong> and <strong>Get Transaction Status</strong> return the order's current status, so you can reconcile.</li>\n</ul>\n","_postman_id":"5e523db0-67e9-4371-9d93-fe4eb273a55a"}],"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test(\"Response time is acceptable\", function () {","    pm.expect(pm.response.responseTime).to.be.below(30000);","});"]}}],"variable":[{"key":"base_url","value":"https://app.psbi.com","type":"string","description":"PSBI API base URL."},{"key":"api_key","value":"your-api-key-here","type":"string","description":"Your API key from the PSBI implementation team."},{"key":"your_webhook_url","value":"https://your-app.example.com/psbi/webhooks","type":"string","description":"Your webhook endpoint (used by the Status Webhooks examples)."},{"key":"last_autopilot_external_id","value":"","type":"string","description":"Set by the enroll requests."},{"key":"last_consumer_id","value":"","type":"string","description":"Set by synchronous enroll requests; used by Transition by Consumer ID."},{"key":"last_transition_id","value":"","type":"string","description":"Set by the transition requests."},{"key":"last_gateway_external_id","value":"","type":"string","description":"Set by the Gateway create requests."}]}