{"info":{"name":"Hodos Public API","description":"Every Hodos API call, ready to run. Paste your API key into the apiKey variable, then run the folders from the top. List calls save an id that later calls use. Test changes use the tag postman-test and notes that say they are safe to ignore.","schema":"https://schema.getpostman.com/json/collection/v2.1.0/collection.json"},"auth":{"type":"bearer","bearer":[{"key":"token","value":"{{apiKey}}","type":"string"}]},"variable":[{"key":"baseUrl","value":"https://hodos.strutq.com/api/v1"},{"key":"apiKey","value":""},{"key":"jobId","value":""},{"key":"contactId","value":""},{"key":"claimId","value":""},{"key":"taskId","value":""},{"key":"newTaskId","value":""},{"key":"teammateId","value":""},{"key":"notificationId","value":""},{"key":"fileId","value":""},{"key":"intentId","value":""},{"key":"requestId","value":""},{"key":"attachmentId","value":""},{"key":"sha256","value":""},{"key":"fileName","value":""},{"key":"fileMime","value":""},{"key":"fileKind","value":""}],"item":[{"name":"Account","item":[{"name":"Check the API key","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/me","host":["{{baseUrl}}"],"path":["me"]},"description":"Returns the key's id, its company (org_id), the scopes it can use right now, and when it expires. Call this first to confirm a key works. Needs no scope."},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));","pm.test('Key works', () => pm.response.to.have.status(200));"]}}]}]},{"name":"Team","item":[{"name":"List teammates","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/team","host":["{{baseUrl}}"],"path":["team"]},"description":"Lists the company's active people with their name and role, so a task's `assignee_id` or a note's `author_user_id` can be shown by name, and `assignee_ids` can be filled when creating a task. One page holds them all. Needs `tasks.read`.\n\nNeeds scope: tasks.read"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));","const rows = pm.response.json().data || [];","if (rows.length) pm.collectionVariables.set(\"teammateId\", rows[0].id);"]}}]}]},{"name":"Jobs","item":[{"name":"List pipelines and stages","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/pipelines","host":["{{baseUrl}}"],"path":["pipelines"]},"description":"Lists the company's job pipelines in board order, each with its stages in order, so a job's `pipeline_id` and `stage_id` can be shown by name. One page holds them all. Needs `jobs.read`.\n\nNeeds scope: jobs.read"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]},{"name":"List jobs","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/jobs","host":["{{baseUrl}}"],"path":["jobs"]},"description":"Lists the company's jobs, newest first. Archived jobs are left out. Each job names its contact, its pipeline and the stage it sits in. Page through with `cursor`. Needs `jobs.read`.\n\nNeeds scope: jobs.read"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));","const rows = pm.response.json().data || [];","if (rows.length) pm.collectionVariables.set(\"jobId\", rows[0].id);"]}}]},{"name":"Get a job","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/jobs/{{jobId}}","host":["{{baseUrl}}"],"path":["jobs","{{jobId}}"]},"description":"Returns one job by id. Needs `jobs.read`.\n\nNeeds scope: jobs.read"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]},{"name":"Change a job's tags","request":{"method":"PATCH","header":[{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/jobs/{{jobId}}/tags","host":["{{baseUrl}}"],"path":["jobs","{{jobId}}","tags"]},"description":"Changes the tags on a job. Send `set` to replace them all, or `add` and/or `remove` to change some. Tags are 1 to 40 characters, up to 50 per job, and matching ignores capitals. Repeating the same call changes nothing more. Needs `jobs.read` and `jobs.update`.\n\nNeeds scope: jobs.update","body":{"mode":"raw","raw":"{\n  \"add\": [\n    \"postman-test\"\n  ]\n}","options":{"raw":{"language":"json"}}}},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]},{"name":"Add a note to a job and tag teammates","request":{"method":"POST","header":[{"key":"Idempotency-Key","value":"{{$guid}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/jobs/{{jobId}}/notes","host":["{{baseUrl}}"],"path":["jobs","{{jobId}}","notes"]},"description":"Adds a plain-text note to the job's timeline. The team sees it; the customer does not. List teammates in `mention_user_ids` (from the team list, up to 20) to tag them: each gets a Hodos alert and, unless they turned it off, an email, the same as a tag typed in the app. Send a unique `Idempotency-Key`. Needs `jobs.read`, `notes.read` and `notes.create`.\n\nNeeds scope: notes.create","body":{"mode":"raw","raw":"{\n  \"body\": \"Postman test note. Safe to ignore.\",\n  \"mention_user_ids\": [\n    \"{{teammateId}}\"\n  ]\n}","options":{"raw":{"language":"json"}}}},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]}]},{"name":"Contacts","item":[{"name":"List contacts","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/contacts","host":["{{baseUrl}}"],"path":["contacts"]},"description":"Lists the company's contacts (homeowners and other customers), newest first. Archived contacts are left out. Page through with `cursor`. Needs `contacts.read`.\n\nNeeds scope: contacts.read"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));","const rows = pm.response.json().data || [];","if (rows.length) pm.collectionVariables.set(\"contactId\", rows[0].id);"]}}]},{"name":"Get a contact","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/contacts/{{contactId}}","host":["{{baseUrl}}"],"path":["contacts","{{contactId}}"]},"description":"Returns one contact by id. Needs `contacts.read`.\n\nNeeds scope: contacts.read"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]},{"name":"Change a contact's tags","request":{"method":"PATCH","header":[{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/contacts/{{contactId}}/tags","host":["{{baseUrl}}"],"path":["contacts","{{contactId}}","tags"]},"description":"Changes the tags on a contact. Send `set` to replace them all, or `add` and/or `remove` to change some. Tags are 1 to 40 characters, up to 50 per contact, and matching ignores capitals. Repeating the same call changes nothing more. Needs `contacts.read` and `contacts.update`.\n\nNeeds scope: contacts.update","body":{"mode":"raw","raw":"{\n  \"add\": [\n    \"postman-test\"\n  ]\n}","options":{"raw":{"language":"json"}}}},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]}]},{"name":"Files","item":[{"name":"List files on a job","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/jobs/{{jobId}}/files","host":["{{baseUrl}}"],"path":["jobs","{{jobId}}","files"]},"description":"Lists file details (not the bytes) on the job, newest first. Download one with the file download call. Needs `jobs.read` and `files.read`.\n\nNeeds scope: files.read"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));","const rows = pm.response.json().data || []; if (rows.length) pm.collectionVariables.set('fileId', rows[0].id);"]}}]},{"name":"List files on a claim's job","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/claims/{{claimId}}/files","host":["{{baseUrl}}"],"path":["claims","{{claimId}}","files"]},"description":"Lists file details (not the bytes) on the claim's job. Download one with the file download call. Needs `jobs.read` and `files.read`.\n\nNeeds scope: files.read"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]},{"name":"Start a file upload on a job","request":{"method":"POST","header":[{"key":"Idempotency-Key","value":"{{requestId}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/jobs/{{jobId}}/files/uploads","host":["{{baseUrl}}"],"path":["jobs","{{jobId}}","files","uploads"]},"description":"Step 1 of 2 for one file. Reserves an upload and returns a one-time `upload_url`. PUT the raw bytes there with `upload_metadata` base64-encoded in an `x-metadata` header, then call finish upload. The `Idempotency-Key` must equal `request_id`. Needs `jobs.read`, `files.read` and `files.create`.\n\nNeeds scope: files.create","body":{"mode":"raw","raw":"{\n  \"request_id\": \"{{requestId}}\",\n  \"attachment_id\": \"{{attachmentId}}\",\n  \"name\": \"{{fileName}}\",\n  \"kind\": \"{{fileKind}}\",\n  \"mime_type\": \"{{fileMime}}\",\n  \"size_bytes\": 68,\n  \"sha256\": \"{{sha256}}\"\n}","options":{"raw":{"language":"json"}}}},"event":[{"listen":"prerequest","script":{"type":"text/javascript","exec":["const b64 = \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==\";","const bytes = CryptoJS.enc.Base64.parse(b64);","pm.collectionVariables.set(\"sha256\", CryptoJS.SHA256(bytes).toString());","pm.collectionVariables.set(\"requestId\", pm.variables.replaceIn(\"{{$guid}}\"));","pm.collectionVariables.set(\"attachmentId\", pm.variables.replaceIn(\"{{$guid}}\"));","pm.collectionVariables.set(\"fileName\", \"postman.png\");","pm.collectionVariables.set(\"fileMime\", \"image/png\");","pm.collectionVariables.set(\"fileKind\", \"photo\");"]}},{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));","pm.collectionVariables.set('intentId', pm.response.json().data.intent_id);"]}}]},{"name":"Finish a file upload","request":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/files/uploads/{{intentId}}/finalize","host":["{{baseUrl}}"],"path":["files","uploads","{{intentId}}","finalize"]},"description":"Step 2 of 2. Checks the uploaded file's type, size and the checksum you declared match what was reserved, and adds the file to the job. Returns 202 if the bytes have not arrived yet; call again shortly. Needs `jobs.read`, `files.read` and `files.create`.\n\nNeeds scope: files.create"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]},{"name":"Start up to 20 file uploads on a job","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/jobs/{{jobId}}/files/uploads/bulk","host":["{{baseUrl}}"],"path":["jobs","{{jobId}}","files","uploads","bulk"]},"description":"Step 1 of 3 for many files. Reserves up to 20 uploads in one call; each answer carries its own status and `upload`, or an `error`. Repeating a `request_id` returns the same reservation. PUT each file's bytes to its `upload_url` (in parallel is fine), then call bulk finish. Needs `jobs.read`, `files.read` and `files.create`.\n\nNeeds scope: files.create","body":{"mode":"raw","raw":"{\n  \"files\": [\n    {\n      \"request_id\": \"{{requestId}}\",\n      \"attachment_id\": \"{{attachmentId}}\",\n      \"name\": \"{{fileName}}\",\n      \"kind\": \"{{fileKind}}\",\n      \"mime_type\": \"{{fileMime}}\",\n      \"size_bytes\": 68,\n      \"sha256\": \"{{sha256}}\"\n    }\n  ]\n}","options":{"raw":{"language":"json"}}}},"event":[{"listen":"prerequest","script":{"type":"text/javascript","exec":["const b64 = \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==\";","const bytes = CryptoJS.enc.Base64.parse(b64);","pm.collectionVariables.set(\"sha256\", CryptoJS.SHA256(bytes).toString());","pm.collectionVariables.set(\"requestId\", pm.variables.replaceIn(\"{{$guid}}\"));","pm.collectionVariables.set(\"attachmentId\", pm.variables.replaceIn(\"{{$guid}}\"));","pm.collectionVariables.set(\"fileName\", \"postman.png\");","pm.collectionVariables.set(\"fileMime\", \"image/png\");","pm.collectionVariables.set(\"fileKind\", \"photo\");"]}},{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));","const r = (pm.response.json().data.results || pm.response.json().data || [])[0] || {}; pm.collectionVariables.set('intentId', (r.upload || r).intent_id || '');"]}}]},{"name":"Finish up to 20 file uploads","request":{"method":"POST","header":[{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/files/uploads/bulk-finalize","host":["{{baseUrl}}"],"path":["files","uploads","bulk-finalize"]},"description":"Step 3 of 3 for many files. Checks each uploaded file against what was reserved and adds it to its job. Each answer carries its own status: 200 registered, 202 bytes not there yet (call again shortly), or an `error`. Needs `jobs.read`, `files.read` and `files.create`.\n\nNeeds scope: files.create","body":{"mode":"raw","raw":"{\n  \"intent_ids\": [\n    \"{{intentId}}\"\n  ]\n}","options":{"raw":{"language":"json"}}}},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]},{"name":"Start a file upload","request":{"method":"POST","header":[{"key":"Idempotency-Key","value":"{{requestId}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/claims/{{claimId}}/files/uploads","host":["{{baseUrl}}"],"path":["claims","{{claimId}}","files","uploads"]},"description":"Step 1 of 2. Reserves an upload for the claim's job and returns a one-time `upload_url`. PUT the raw bytes there with `upload_metadata` base64-encoded in an `x-metadata` header, then call finish upload. The `Idempotency-Key` must equal `request_id`. Needs `jobs.read`, `files.read` and `files.create`.\n\nNeeds scope: files.create","body":{"mode":"raw","raw":"{\n  \"request_id\": \"{{requestId}}\",\n  \"attachment_id\": \"{{attachmentId}}\",\n  \"name\": \"{{fileName}}\",\n  \"kind\": \"{{fileKind}}\",\n  \"mime_type\": \"{{fileMime}}\",\n  \"size_bytes\": 68,\n  \"sha256\": \"{{sha256}}\"\n}","options":{"raw":{"language":"json"}}}},"event":[{"listen":"prerequest","script":{"type":"text/javascript","exec":["const b64 = \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==\";","const bytes = CryptoJS.enc.Base64.parse(b64);","pm.collectionVariables.set(\"sha256\", CryptoJS.SHA256(bytes).toString());","pm.collectionVariables.set(\"requestId\", pm.variables.replaceIn(\"{{$guid}}\"));","pm.collectionVariables.set(\"attachmentId\", pm.variables.replaceIn(\"{{$guid}}\"));","pm.collectionVariables.set(\"fileName\", \"postman.png\");","pm.collectionVariables.set(\"fileMime\", \"image/png\");","pm.collectionVariables.set(\"fileKind\", \"photo\");"]}},{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));","pm.collectionVariables.set('intentId', pm.response.json().data.intent_id);"]}}]},{"name":"Download a file","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/files/{{fileId}}/download","host":["{{baseUrl}}"],"path":["files","{{fileId}}","download"]},"description":"Returns the bytes of one file this key can see. Needs `jobs.read`, `files.read` and `files.download`.\n\nNeeds scope: files.download"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]}]},{"name":"Claims","item":[{"name":"List insurance claims","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/claims","host":["{{baseUrl}}"],"path":["claims"]},"description":"Lists insurance claims on jobs this key can see. Filter with `claim_number`. A claim number is not unique across carriers, so check the job before choosing a match. Needs `jobs.read`.\n\nNeeds scope: jobs.read"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));","const rows = pm.response.json().data || [];","if (rows.length) pm.collectionVariables.set(\"claimId\", rows[0].id);"]}}]},{"name":"Get an insurance claim","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/claims/{{claimId}}","host":["{{baseUrl}}"],"path":["claims","{{claimId}}"]},"description":"Returns one claim with the job it belongs to. Needs `jobs.read`.\n\nNeeds scope: jobs.read"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]},{"name":"Add a note to a claim's job","request":{"method":"POST","header":[{"key":"Idempotency-Key","value":"{{$guid}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/claims/{{claimId}}/notes","host":["{{baseUrl}}"],"path":["claims","{{claimId}}","notes"]},"description":"Adds a plain-text note to the claim's job timeline. The team sees it; the customer does not. Send a unique `Idempotency-Key`. Needs `jobs.read`, `notes.read` and `notes.create`.\n\nNeeds scope: notes.create","body":{"mode":"raw","raw":"{\n  \"body\": \"Postman test note. Safe to ignore.\"\n}","options":{"raw":{"language":"json"}}}},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]},{"name":"List notes on a claim's job","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/claims/{{claimId}}/notes","host":["{{baseUrl}}"],"path":["claims","{{claimId}}","notes"]},"description":"Lists the shared notes on the claim's job, newest first. Needs `jobs.read` and `notes.read`.\n\nNeeds scope: notes.read"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]}]},{"name":"Tasks","item":[{"name":"Create a task","request":{"method":"POST","header":[{"key":"Idempotency-Key","value":"{{$guid}}"},{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/tasks","host":["{{baseUrl}}"],"path":["tasks"]},"description":"Creates a task, optionally tied to a job or contact and assigned to teammates. Send a unique `Idempotency-Key`: repeating the same key with the same body returns the task already made (200) instead of a second copy, and reusing it with a different body returns 409. Needs `tasks.create`.\n\nNeeds scope: tasks.create","body":{"mode":"raw","raw":"{\n  \"title\": \"Postman test task\",\n  \"priority\": \"normal\",\n  \"job_id\": \"{{jobId}}\",\n  \"assignee_ids\": [\n    \"{{teammateId}}\"\n  ]\n}","options":{"raw":{"language":"json"}}}},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));","pm.collectionVariables.set(\"newTaskId\", (pm.response.json().data || {}).id);"]}}]},{"name":"List tasks","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/tasks","host":["{{baseUrl}}"],"path":["tasks"]},"description":"Lists the company's tasks, newest first, open and completed. A task with `completed_at` set is done. Page through with `cursor`. Needs `tasks.read`.\n\nNeeds scope: tasks.read"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));","const rows = pm.response.json().data || [];","if (rows.length) pm.collectionVariables.set(\"taskId\", rows[0].id);"]}}]},{"name":"Get a task","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/tasks/{{taskId}}","host":["{{baseUrl}}"],"path":["tasks","{{taskId}}"]},"description":"Returns one task by id. Needs `tasks.read`.\n\nNeeds scope: tasks.read"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]},{"name":"Mark the new task done","request":{"method":"PATCH","header":[{"key":"Content-Type","value":"application/json"}],"url":{"raw":"{{baseUrl}}/tasks/{{newTaskId}}","host":["{{baseUrl}}"],"path":["tasks","{{newTaskId}}"]},"description":"Send `{\"completed\": true}` to mark the task done or `{\"completed\": false}` to reopen it. Doing the same thing twice is harmless. The job timeline records it as done by the key's owner. Needs `tasks.update`.\n\nNeeds scope: tasks.update","body":{"mode":"raw","raw":"{\n  \"completed\": true\n}","options":{"raw":{"language":"json"}}}},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]},{"name":"Delete the new task","request":{"method":"DELETE","header":[],"url":{"raw":"{{baseUrl}}/tasks/{{newTaskId}}","host":["{{baseUrl}}"],"path":["tasks","{{newTaskId}}"]},"description":"Deletes a task the way the app does: it is archived, not destroyed, and no longer appears in task lists or reads. Deleting it twice returns not found. Needs `tasks.archive`.\n\nNeeds scope: tasks.archive"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]}]},{"name":"Notifications","item":[{"name":"List the key owner's notifications","request":{"method":"GET","header":[],"url":{"raw":"{{baseUrl}}/notifications","host":["{{baseUrl}}"],"path":["notifications"]},"description":"Lists the Hodos notifications of the person who owns the key, newest first, each with whether it has been read. Add `unread=true` for only the unread ones. `href` is a Hodos path, such as `/jobs/{id}`. Needs `notifications.read`.\n\nNeeds scope: notifications.read"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));","const rows = pm.response.json().data || [];","if (rows.length) pm.collectionVariables.set(\"notificationId\", rows[0].id);"]}}]},{"name":"Mark a notification read","request":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/notifications/{{notificationId}}/read","host":["{{baseUrl}}"],"path":["notifications","{{notificationId}}","read"]},"description":"Marks one notification read, by the `id` the list gave. Needs `notifications.read` and `notifications.update`.\n\nNeeds scope: notifications.update"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]},{"name":"Mark all notifications read","request":{"method":"POST","header":[],"url":{"raw":"{{baseUrl}}/notifications/read-all","host":["{{baseUrl}}"],"path":["notifications","read-all"]},"description":"Marks everything up to now read for the key owner. Needs `notifications.read` and `notifications.update`.\n\nNeeds scope: notifications.update"},"event":[{"listen":"test","script":{"type":"text/javascript","exec":["pm.test('Status is 2xx', () => pm.expect(pm.response.code).to.be.within(200, 299));"]}}]}]}]}