{ "info": { "name": "be-crm — Leads & Lead Verification (data door)", "description": "All Leads (crm.lead.*) and Lead Verification (crm.leadVerification.*) data-door actions — for local testing against be-crm on :4010 (trust-headers mode).\n\nHOW TO USE:\n1. Import this file into Postman.\n2. Run 'Verification writes / 10. intake' FIRST — its test script captures the new record id into {{seedVerificationId}}, which the assign/verify chain references. The cleanest end-to-end path is: 10 intake → 11 assign → 12 reassign → 15 verify (promotes to a Lead, captured into {{promotedLeadId}}).\n3. The read folders ('Leads reads' / 'Verification reads') capture {{leadId}} / {{verificationId}} from search results when data exists.\n\nVERIFICATION STATE MACHINE (enforced server-side, §3.1): a record flows pending → assigned → in_progress, and ends at ONE terminal outcome — either verified (via 15. verify, which promotes a Lead) OR unverified (via 17. markUnverified). 'verified' is TERMINAL: markUnverified / moveToPending / updateStatus against a verified record return 409 invalid_state_transition. So run ONE terminal action per record. To exercise markUnverified or moveToPending, run 10. intake to seed a FRESH pending record (repoints {{verificationId}}) and act on it BEFORE verifying — e.g. intake → assign → moveToPending, or intake → markUnverified. Valid: markUnverified from pending/assigned/in_progress; moveToPending from assigned/in_progress.\n\nNOTE: everything is POST except GET /health. Reads hit /query, writes hit /command (action+data in the JSON body). Commands auto-generate a fresh X-Idempotency-Key ({{$guid}}). In dev, the identity headers stand in for what the Shell BFF injects in real environments — never ship them from the frontend. Both modules are gated by the leads.manage permission.", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "variable": [ { "key": "baseUrl", "value": "http://localhost:4010" }, { "key": "tenant", "value": "tnt_demo" }, { "key": "principal", "value": "prn_owner" }, { "key": "leadId", "value": "" }, { "key": "verificationId", "value": "" }, { "key": "seedVerificationId", "value": "" }, { "key": "promotedLeadId", "value": "" }, { "key": "createdLeadId", "value": "" }, { "key": "attachmentId", "value": "" } ], "item": [ { "name": "0. Health (the only GET)", "request": { "method": "GET", "header": [], "url": { "raw": "{{baseUrl}}/health", "host": ["{{baseUrl}}"], "path": ["health"] }, "description": "Liveness + the full list of registered actions (crm.lead.* + crm.leadVerification.*). Works in a browser." } }, { "name": "Leads reads (POST /query)", "item": [ { "name": "1. lead.stats (header stat strip)", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.lead.stats\",\n \"variables\": {}\n}" }, "url": { "raw": "{{baseUrl}}/query", "host": ["{{baseUrl}}"], "path": ["query"] }, "description": "Returns { total, byStatus: { new, contacted, appointed, closed } }." } }, { "name": "2. lead.search (captures leadId)", "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const j = pm.response.json();", "if (j.items && j.items[0]) pm.collectionVariables.set('leadId', j.items[0].id);", "pm.test('has paging meta', () => pm.expect(j.meta).to.have.property('total'));" ] } } ], "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.lead.search\",\n \"variables\": { \"status\": \"new\", \"page\": 1, \"perPage\": 50 }\n}" }, "url": { "raw": "{{baseUrl}}/query", "host": ["{{baseUrl}}"], "path": ["query"] }, "description": "Board list + search + status/priority/source/assigneeId filters. Returns { items: LeadCardDTO[], meta }." } }, { "name": "3. lead.get (full detail)", "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const j = pm.response.json();", "if (j.attachments && j.attachments[0]) pm.collectionVariables.set('attachmentId', j.attachments[0].id);" ] } } ], "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.lead.get\",\n \"variables\": { \"id\": \"{{leadId}}\" }\n}" }, "url": { "raw": "{{baseUrl}}/query", "host": ["{{baseUrl}}"], "path": ["query"] }, "description": "Full record incl. phones/emails/attachments + resolved assignee/canvasser/creator names + storm/property/job/insurance groupings." } } ] }, { "name": "Verification reads (POST /query)", "item": [ { "name": "4. leadVerification.stats (stat tiles)", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.leadVerification.stats\",\n \"variables\": {}\n}" }, "url": { "raw": "{{baseUrl}}/query", "host": ["{{baseUrl}}"], "path": ["query"] }, "description": "Returns { verified, in_progress, assigned, pending, unverified }." } }, { "name": "5. leadVerification.search (captures verificationId)", "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const j = pm.response.json();", "if (j.items && j.items[0]) pm.collectionVariables.set('verificationId', j.items[0].id);", "pm.test('has paging meta', () => pm.expect(j.meta).to.have.property('total'));" ] } } ], "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.leadVerification.search\",\n \"variables\": { \"page\": 1, \"perPage\": 50 }\n}" }, "url": { "raw": "{{baseUrl}}/query", "host": ["{{baseUrl}}"], "path": ["query"] }, "description": "Queue table + search + status/source/assignee filters. Returns { items: VerificationRowDTO[], meta }." } }, { "name": "6. leadVerification.get (detail + timeline)", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.leadVerification.get\",\n \"variables\": { \"id\": \"{{verificationId}}\" }\n}" }, "url": { "raw": "{{baseUrl}}/query", "host": ["{{baseUrl}}"], "path": ["query"] } } }, { "name": "7. leadVerification.activity.list", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.leadVerification.activity.list\",\n \"variables\": { \"id\": \"{{verificationId}}\" }\n}" }, "url": { "raw": "{{baseUrl}}/query", "host": ["{{baseUrl}}"], "path": ["query"] } } } ] }, { "name": "Verification writes (POST /command)", "item": [ { "name": "10. leadVerification.intake (RUN FIRST — captures seedVerificationId)", "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const j = pm.response.json();", "if (j && j.id) { pm.collectionVariables.set('seedVerificationId', j.id); pm.collectionVariables.set('verificationId', j.id); }", "pm.test('starts pending', () => pm.expect(j.status).to.eql('pending'));" ] } } ], "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.leadVerification.intake\",\n \"variables\": {\n \"name\": \"Kevin Hartley\",\n \"phone\": \"(972) 413-8902\",\n \"email\": \"kevin.hartley@example.com\",\n \"address\": \"2814 Ravenswood Dr, Plano, TX 75023\",\n \"source\": \"Door Knock\"\n }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] }, "description": "Records 'arrive' via intake (§12.11) — the ingestion path outside the two UI screens. Seeds the queue and captures {{seedVerificationId}} for the working flow below." } }, { "name": "11. leadVerification.assign (pending → assigned)", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.leadVerification.assign\",\n \"variables\": { \"id\": \"{{seedVerificationId}}\", \"assigneeId\": \"{{principal}}\" }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] }, "description": "Change Assignee. A pending record moves to 'assigned'; otherwise only the assignee changes." } }, { "name": "12. leadVerification.reassign (→ In Progress)", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.leadVerification.reassign\",\n \"variables\": { \"id\": \"{{seedVerificationId}}\", \"assigneeId\": \"{{principal}}\" }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] } } }, { "name": "13. leadVerification.updateStatus (generic; not 'verified')", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.leadVerification.updateStatus\",\n \"variables\": { \"id\": \"{{seedVerificationId}}\", \"status\": \"in_progress\", \"subStatus\": \"Reviewing Insurance\" }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] }, "description": "Generic status + sub-status set. Setting 'verified' here → 422 (use 15. verify, which promotes a Lead). Disallowed transitions → 409." } }, { "name": "14. leadVerification.note.add", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.leadVerification.note.add\",\n \"variables\": { \"id\": \"{{seedVerificationId}}\", \"text\": \"Ownership confirmed via county records.\" }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] } } }, { "name": "15. leadVerification.verify (PROMOTES to Lead — captures promotedLeadId)", "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const j = pm.response.json();", "if (j.lead && j.lead.id) pm.collectionVariables.set('promotedLeadId', j.lead.id);", "pm.test('verification is verified', () => pm.expect(j.verification.status).to.eql('verified'));", "pm.test('promoted lead is new', () => pm.expect(j.lead.status).to.eql('new'));" ] } } ], "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.leadVerification.verify\",\n \"variables\": { \"id\": \"{{seedVerificationId}}\", \"notes\": \"Verified — insurance + damage confirmed.\" }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] }, "description": "The critical cross-module command: flips the record to verified and creates a new Lead (status=new) in one transaction, linking both records. Returns { verification, lead }. A second call → 409 already_verified." } }, { "name": "16. leadVerification.moveToPending", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.leadVerification.moveToPending\",\n \"variables\": { \"id\": \"{{verificationId}}\" }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] }, "description": "Send an assigned/in_progress record back to pending (keeps the assignee). Point at a non-verified {{verificationId}}." } }, { "name": "17. leadVerification.markUnverified", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.leadVerification.markUnverified\",\n \"variables\": { \"id\": \"{{verificationId}}\", \"reason\": \"Could not confirm ownership.\" }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] }, "description": "Terminal outcome. Allowed from pending/assigned/in_progress; a verified record → 409. Point {{verificationId}} at a NON-verified record (run 10. intake to seed a fresh pending one)." } } ] }, { "name": "Leads writes (POST /command)", "item": [ { "name": "20. lead.create (Full Form — captures createdLeadId)", "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const j = pm.response.json();", "if (j && j.id) { pm.collectionVariables.set('createdLeadId', j.id); pm.collectionVariables.set('leadId', j.id); }", "pm.test('priority normalized to lowercase', () => pm.expect(j.priority).to.eql('high'));" ] } } ], "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.lead.create\",\n \"variables\": {\n \"firstName\": \"John\",\n \"lastName\": \"Martinez\",\n \"phones\": [\n { \"number\": \"(469) 500-1000\", \"type\": \"Mobile\", \"primary\": true },\n { \"number\": \"(469) 500-2000\", \"type\": \"Home\" }\n ],\n \"emails\": [ { \"address\": \"john.martinez@example.com\", \"primary\": true } ],\n \"property\": { \"address\": \"4821 Spring Creek Pkwy\", \"city\": \"Plano\", \"state\": \"TX\", \"zip\": \"75023\", \"type\": \"Single Family\" },\n \"job\": { \"source\": \"Door Knock\", \"canvasserId\": \"{{principal}}\", \"leadType\": \"Insurance\", \"workType\": \"Roof Replacement\", \"tradeType\": \"Roofing\", \"urgency\": \"High\", \"notes\": \"Significant granule loss on the south slope.\" },\n \"insurance\": { \"company\": \"State Farm\", \"claimStatus\": \"Filed\", \"claimNumber\": \"CLM-2026-1000\", \"policyNumber\": \"POL-080000\", \"adjusterName\": \"Marcus Powell\", \"adjusterPhone\": \"(972) 700-3000\" },\n \"assignment\": { \"assigneeId\": \"{{principal}}\", \"priority\": \"High\", \"followUp\": \"2026-06-04\" }\n }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] }, "description": "Quick + Full form intake. Only a non-empty name (firstName OR lastName) is required (else 422 name_required). Title-case priority is normalized to lowercase; the first phone is marked primary when none is flagged. DUPLICATE GUARD: if an active (non-closed) lead in the tenant has BOTH the same normalized primary phone AND the same address, the create is REJECTED with 409 duplicate_lead (the response's duplicateOf lists the existing lead) and no lead is inserted." } }, { "name": "21. lead.update (partial patch)", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.lead.update\",\n \"variables\": { \"id\": \"{{createdLeadId}}\", \"patch\": { \"priority\": \"Medium\", \"jobNotes\": \"Adjuster meeting scheduled.\" } }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] } } }, { "name": "22. lead.updateStatus (audited)", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.lead.updateStatus\",\n \"variables\": { \"id\": \"{{createdLeadId}}\", \"status\": \"contacted\", \"note\": \"Spoke with homeowner.\" }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] }, "description": "Appends a row to lead_status_history." } }, { "name": "23. lead.assign (rep; null for Unassigned)", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.lead.assign\",\n \"variables\": { \"id\": \"{{createdLeadId}}\", \"assigneeId\": \"{{principal}}\" }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] } } }, { "name": "24. lead.attachment.add (site photo — captures attachmentId)", "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const j = pm.response.json();", "if (j.attachments && j.attachments.length) pm.collectionVariables.set('attachmentId', j.attachments[j.attachments.length - 1].id);" ] } } ], "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.lead.attachment.add\",\n \"variables\": { \"id\": \"{{createdLeadId}}\", \"attachment\": { \"contentRef\": \"obj/site-photo-1.jpg\", \"mimeType\": \"image/jpeg\", \"sizeBytes\": 1048576, \"filename\": \"roof-south.jpg\" } }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] }, "description": "contentRef is the object key returned by crm.media.presignUpload after the browser PUTs the bytes (§11.5). Files over 25 MB → 413 file_too_large." } }, { "name": "25. lead.attachment.remove", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.lead.attachment.remove\",\n \"variables\": { \"id\": \"{{createdLeadId}}\", \"attachmentId\": \"{{attachmentId}}\" }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] }, "description": "Run 24 first so {{attachmentId}} is captured." } } ] }, { "name": "Negative / guard checks", "item": [ { "name": "Verify an already-verified record (expect 409 already_verified)", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.leadVerification.verify\",\n \"variables\": { \"id\": \"{{seedVerificationId}}\" }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] }, "description": "Run 15. verify first — a second verify on the same record is rejected (no duplicate Lead)." } }, { "name": "Create with no name (expect 422 name_required)", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.lead.create\",\n \"variables\": { \"firstName\": \"\", \"lastName\": \" \" }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] } } }, { "name": "Create with a bad email (expect 400 validation)", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.lead.create\",\n \"variables\": { \"firstName\": \"Jane\", \"emails\": [ { \"address\": \"not-an-email\" } ] }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] } } }, { "name": "Verify via generic updateStatus (expect 422 — use verify)", "request": { "method": "POST", "header": [ { "key": "Authorization", "value": "Bearer dev" }, { "key": "X-App-ID", "value": "crm-web" }, { "key": "X-Tenant-ID", "value": "{{tenant}}" }, { "key": "X-Principal-ID", "value": "{{principal}}" }, { "key": "X-Idempotency-Key", "value": "{{$guid}}" }, { "key": "Content-Type", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n \"action\": \"crm.leadVerification.updateStatus\",\n \"variables\": { \"id\": \"{{verificationId}}\", \"status\": \"verified\" }\n}" }, "url": { "raw": "{{baseUrl}}/command", "host": ["{{baseUrl}}"], "path": ["command"] }, "description": "The generic status setter refuses 'verified' — verification must go through crm.leadVerification.verify (which promotes a Lead)." } } ] } ] }