############################################################################### # Leads & Lead Verification — data-door requests (VS Code "REST Client" / JetBrains HTTP). # Install the REST Client extension, then click "Send Request" above each block. # Requests are chained: run the "@name" reads/writes in order so the id variables # below resolve. The cleanest end-to-end path is: # #10 intake → #11 assign → #12 reassign → #15 verify (promotes to a Lead). # Dev trust-headers mode — change @principal to test different callers. ############################################################################### @baseUrl = http://localhost:4010 @tenant = tnt_demo @principal = prn_owner # Shared headers are repeated per request (the format has no header includes). ### Health — see every registered action (crm.lead.* + crm.leadVerification.*) GET {{baseUrl}}/health # ─────────────────────────────── LEADS: READS (POST /query) ───────────────────── ### 1. Lead stats (header stat strip: total + byStatus) POST {{baseUrl}}/query Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} Content-Type: application/json { "action": "crm.lead.stats", "variables": {} } ### 2. Lead search (RUN THIS — provides @leadId). Supports query/status/priority/source/assigneeId/page/perPage. # @name leadSearch POST {{baseUrl}}/query Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} Content-Type: application/json { "action": "crm.lead.search", "variables": { "status": "new", "page": 1, "perPage": 50 } } @leadId = {{leadSearch.response.body.$.items[0].id}} ### 3. Lead get (full detail incl. phones/emails/attachments + resolved names) POST {{baseUrl}}/query Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} Content-Type: application/json { "action": "crm.lead.get", "variables": { "id": "{{leadId}}" } } # ────────────────────── LEAD VERIFICATION: READS (POST /query) ────────────────── ### 4. Verification stats (the clickable stat tiles) POST {{baseUrl}}/query Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} Content-Type: application/json { "action": "crm.leadVerification.stats", "variables": {} } ### 5. Verification search (RUN THIS — provides @verificationId) # @name vSearch POST {{baseUrl}}/query Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} Content-Type: application/json { "action": "crm.leadVerification.search", "variables": { "page": 1, "perPage": 50 } } @verificationId = {{vSearch.response.body.$.items[0].id}} ### 6. Verification get (detail + activity timeline) POST {{baseUrl}}/query Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} Content-Type: application/json { "action": "crm.leadVerification.get", "variables": { "id": "{{verificationId}}" } } ### 7. Verification activity list (also embedded in .get) POST {{baseUrl}}/query Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} Content-Type: application/json { "action": "crm.leadVerification.activity.list", "variables": { "id": "{{verificationId}}" } } # ─────────────── VERIFICATION: WRITES (POST /command + unique key) ─────────────── # Commands require a UNIQUE X-Idempotency-Key each time (reuse ⇒ 409). # Records "arrive" via intake (§12.11) — start the working flow at #10. ### 10. Intake — seed a verification record (RUN — provides @seedVerificationId) # @name intake POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: lv-intake-001 Content-Type: application/json { "action": "crm.leadVerification.intake", "variables": { "name": "Kevin Hartley", "phone": "(972) 413-8902", "email": "kevin.hartley@example.com", "address": "2814 Ravenswood Dr, Plano, TX 75023", "source": "Door Knock" } } @seedVerificationId = {{intake.response.body.$.id}} ### 11. Assign (Change Assignee) — pending → assigned POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: lv-assign-001 Content-Type: application/json { "action": "crm.leadVerification.assign", "variables": { "id": "{{seedVerificationId}}", "assigneeId": "{{principal}}" } } ### 12. Reassign → In Progress POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: lv-reassign-001 Content-Type: application/json { "action": "crm.leadVerification.reassign", "variables": { "id": "{{seedVerificationId}}", "assigneeId": "{{principal}}" } } ### 13. Update status + sub-status (generic; cannot set 'verified' here — use #15) POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: lv-updatestatus-001 Content-Type: application/json { "action": "crm.leadVerification.updateStatus", "variables": { "id": "{{seedVerificationId}}", "status": "in_progress", "subStatus": "Reviewing Insurance" } } ### 14. Add a verification note (appends + timeline entry) POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: lv-note-001 Content-Type: application/json { "action": "crm.leadVerification.note.add", "variables": { "id": "{{seedVerificationId}}", "text": "Ownership confirmed via county records." } } ### 15. Verify — PROMOTES to a new Lead (status = new). Returns { verification, lead }. # @name verify POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: lv-verify-001 Content-Type: application/json { "action": "crm.leadVerification.verify", "variables": { "id": "{{seedVerificationId}}", "notes": "Verified — insurance + damage confirmed." } } @promotedLeadId = {{verify.response.body.$.lead.id}} ### 16. Get the promoted Lead (created by #15) POST {{baseUrl}}/query Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} Content-Type: application/json { "action": "crm.lead.get", "variables": { "id": "{{promotedLeadId}}" } } ### 17. Move to Pending (send an assigned/in_progress record back). Seed a second record first if needed. POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: lv-topending-001 Content-Type: application/json { "action": "crm.leadVerification.moveToPending", "variables": { "id": "{{verificationId}}" } } ### 18. Mark Unverified (any non-terminal record) POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: lv-unverified-001 Content-Type: application/json { "action": "crm.leadVerification.markUnverified", "variables": { "id": "{{verificationId}}", "reason": "Could not confirm ownership." } } # ─────────────────────── LEADS: WRITES (POST /command + unique key) ────────────── ### 20. Create Lead — Full Form payload (RUN — provides @createdLeadId) # @name createLead POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: lead-create-001 Content-Type: application/json { "action": "crm.lead.create", "variables": { "firstName": "John", "lastName": "Martinez", "phones": [ { "number": "(469) 500-1000", "type": "Mobile", "primary": true }, { "number": "(469) 500-2000", "type": "Home" } ], "emails": [ { "address": "john.martinez@example.com", "primary": true } ], "property": { "address": "4821 Spring Creek Pkwy", "city": "Plano", "state": "TX", "zip": "75023", "type": "Single Family" }, "job": { "source": "Door Knock", "canvasserId": "{{principal}}", "leadType": "Insurance", "workType": "Roof Replacement", "tradeType": "Roofing", "urgency": "High", "notes": "Significant granule loss on the south slope." }, "insurance": { "company": "State Farm", "claimStatus": "Filed", "claimNumber": "CLM-2026-1000", "policyNumber": "POL-080000", "adjusterName": "Marcus Powell", "adjusterPhone": "(972) 700-3000" }, "assignment": { "assigneeId": "{{principal}}", "priority": "High", "followUp": "2026-06-04" } } } @createdLeadId = {{createLead.response.body.$.id}} ### 21. Update Lead (partial patch; priority title-case is normalized to lowercase) POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: lead-update-001 Content-Type: application/json { "action": "crm.lead.update", "variables": { "id": "{{createdLeadId}}", "patch": { "priority": "Medium", "jobNotes": "Adjuster meeting scheduled." } } } ### 22. Update Status (audited → lead_status_history) POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: lead-status-001 Content-Type: application/json { "action": "crm.lead.updateStatus", "variables": { "id": "{{createdLeadId}}", "status": "contacted", "note": "Spoke with homeowner." } } ### 23. Assign a rep (or null for Unassigned) POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: lead-assign-001 Content-Type: application/json { "action": "crm.lead.assign", "variables": { "id": "{{createdLeadId}}", "assigneeId": "{{principal}}" } } ### 24. Attachment add (site photo) — contentRef comes from crm.media.presignUpload (§11.5) POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: lead-attach-add-001 Content-Type: application/json { "action": "crm.lead.attachment.add", "variables": { "id": "{{createdLeadId}}", "attachment": { "contentRef": "obj/site-photo-1.jpg", "mimeType": "image/jpeg", "sizeBytes": 1048576, "filename": "roof-south.jpg" } } } ### 25. Attachment remove (attachmentId from the lead's attachments[] in #3/#16) POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: lead-attach-remove-001 Content-Type: application/json { "action": "crm.lead.attachment.remove", "variables": { "id": "{{createdLeadId}}", "attachmentId": "REPLACE_WITH_ATTACHMENT_ID" } } # ─────────────────────────── NEGATIVE / GUARD CHECKS ──────────────────────────── ### Verify an already-verified record (expect 409 already_verified) POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: guard-alreadyverified-001 Content-Type: application/json { "action": "crm.leadVerification.verify", "variables": { "id": "{{seedVerificationId}}" } } ### Create with no name (expect 422 name_required) POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: guard-noname-001 Content-Type: application/json { "action": "crm.lead.create", "variables": { "firstName": "", "lastName": " " } } ### Create with a bad email (expect 400 validation) POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: guard-bademail-001 Content-Type: application/json { "action": "crm.lead.create", "variables": { "firstName": "Jane", "emails": [ { "address": "not-an-email" } ] } } ### Invalid state transition — verified is terminal via generic updateStatus (expect 409 / 422) POST {{baseUrl}}/command Authorization: Bearer dev X-App-ID: crm-web X-Tenant-ID: {{tenant}} X-Principal-ID: {{principal}} X-Idempotency-Key: guard-transition-001 Content-Type: application/json { "action": "crm.leadVerification.updateStatus", "variables": { "id": "{{seedVerificationId}}", "status": "pending" } }