{
  "info": {
    "_postman_id": "b4e2f0d3-8c1a-4d5b-9e6f-3a1b7c8d2e21",
    "name": "Linra Omni — Vendor API",
    "description": "The Linra Omni vendor-facing API, reached through the dedicated vendor Gateway's `/api/v1` facade. Generated from `src/VendorPortal/vendor-portal-web/openapi/vendor-openapi.source.yaml` and the vendor Gateway route table (`src/ApiGateway/Linra.Omni.VendorGateway/appsettings.json`), structured to mirror the [vendor docs](https://vendors-omni.linra.net) navigation: Platform (Authentication, Profile, Credentials) then Scent (Offers, Cost, Stock, Orders, Returns, Webhooks).\n\nImport the sibling environment file, fill in `clientId`/`clientSecret` (your first vendor credential is always Linra-issued during onboarding — you cannot self-generate one until you already hold an active credential), run **Platform > Authentication > Exchange a client ID and secret for a vendor access token** once, and every other request will authenticate automatically via the collection-level Bearer auth referencing `{{accessToken}}`.\n\n**X-Linra-Environment**: only `Platform > Credentials > List your own API credentials` and `Generate a new API credential for yourself` require it (Identity is a single global service holding credentials for every environment) — every other request (Offers/Cost/Stock/Orders/Returns/Webhooks, and the two other Credentials actions) does not, since Scent is deployed once per environment and has nothing to disambiguate. Verified against the actual controller/service code, not just the OpenAPI description text (which mentions the header more broadly).",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{accessToken}}",
        "type": "string"
      }
    ]
  },
  "event": [],
  "variable": [],
  "item": [
    {
      "name": "Platform",
      "item": [
        {
          "name": "Authentication",
          "item": [
            {
              "name": "Exchange a client ID and secret for a vendor access token",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/auth/token",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "auth",
                    "token"
                  ]
                },
                "description": "**Exchange a client ID and secret for a vendor access token**\n\nThe credential-exchange door. Anonymous — no bearer token is required (you don't have one\nyet). Rate-limited independently of every other route (see the Rate limits section of the\nVendor Integration Standard). Returns a JWT (audience `vendor-api`) valid for 5 minutes,\ncarrying your credential's granted capabilities — see Authentication & capabilities.\n\n_Anonymous endpoint — no bearer token required._",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"clientId\": \"{{clientId}}\",\n  \"clientSecret\": \"{{clientSecret}}\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                },
                "auth": {
                  "type": "noauth"
                }
              },
              "response": [],
              "event": [
                {
                  "listen": "test",
                  "script": {
                    "type": "text/javascript",
                    "exec": [
                      "// Auto-extracts the access token from the ApiResponse envelope and stores it in the",
                      "// environment so every other request (collection/folder-level Bearer auth) works immediately.",
                      "let json;",
                      "try {",
                      "    json = pm.response.json();",
                      "} catch (e) {",
                      "    console.warn('Token response was not JSON — nothing saved.');",
                      "    json = null;",
                      "}",
                      "",
                      "pm.test('Status code is 200', function () {",
                      "    pm.response.to.have.status(200);",
                      "});",
                      "",
                      "if (json && json.payload && json.payload.accessToken) {",
                      "    pm.environment.set('accessToken', json.payload.accessToken);",
                      "    console.log('accessToken saved to the environment.');",
                      "} else {",
                      "    console.warn('No payload.accessToken found in the response — accessToken was NOT updated.');",
                      "}"
                    ]
                  }
                }
              ]
            }
          ]
        },
        {
          "name": "Profile",
          "item": [
            {
              "name": "Get your own vendor profile",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/profile",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "profile"
                  ]
                },
                "description": "**Get your own vendor profile**\n\nReturns your vendor's own Name/IsActive/VAT-registration facts. Read-only — no capability\nrequired (reading your own compliance status carries none of the risk writing it would). There\nis NO write counterpart on this API at any capability level: vendor profile edits (including\ntax/VAT registration, already internal-admin-only before this) are exclusively an\ninternal-admin action, performed via the Linra Omni Portal dashboard, never through this API."
              },
              "response": []
            }
          ]
        },
        {
          "name": "Credentials",
          "item": [
            {
              "name": "List your own API credentials",
              "request": {
                "method": "GET",
                "header": [
                  {
                    "key": "X-Linra-Environment",
                    "value": "{{linraEnvironment}}"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/credentials",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "credentials"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "disabled": true
                    },
                    {
                      "key": "pageSize",
                      "value": "20",
                      "disabled": true
                    }
                  ]
                },
                "description": "**List your own API credentials**\n\nLists your own credentials (including deactivated ones) for the environment named by the\nrequired X-Linra-Environment header. Secrets are never returned. No capability required — no\n`activeOnly` filter (you benefit from seeing your own deactivated credentials too, e.g. to\nconfirm a rotation completed)."
              },
              "response": []
            },
            {
              "name": "Generate a new API credential for yourself",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "X-Linra-Environment",
                    "value": "{{linraEnvironment}}"
                  },
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/credentials",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "credentials"
                  ]
                },
                "description": "**Generate a new API credential for yourself**\n\nGenerates a new capability-scoped credential for your vendor in the environment named by the\nrequired X-Linra-Environment header. The secret is returned once and never again. The\nrequested capabilities may never exceed the UNION of what your OTHER active credentials\nalready hold (across every environment) — a superset request is rejected\n(`403 FORBIDDEN_CAPABILITY_NOT_GRANTED_TO_VENDOR`); if you have zero active credentials\nanywhere, this is rejected too (`422 BUSINESS_VENDOR_FIRST_CREDENTIAL_REQUIRES_ONBOARDING`) —\nyour very first credential is always issued by Linra during onboarding. Requires the Account\ncapability.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"capabilities\": \"Offers, Cost\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "Activate or deactivate one of your own API credentials",
              "request": {
                "method": "PATCH",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/credentials/:id/active",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "credentials",
                    ":id",
                    "active"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                      "description": "The credential id."
                    }
                  ]
                },
                "description": "**Activate or deactivate one of your own API credentials**\n\nSets a credential's active state. A credential belonging to another vendor is reported as 404 — indistinguishable from a nonexistent one. Requires the Account capability.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"isActive\": false\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "Revoke one of your own API credentials",
              "request": {
                "method": "DELETE",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/credentials/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "credentials",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                      "description": "The credential id."
                    }
                  ]
                },
                "description": "**Revoke one of your own API credentials**\n\nPermanently revokes (hard-deletes) one of your own API credentials. A credential belonging to another vendor is reported as 404. Requires the Account capability."
              },
              "response": []
            }
          ]
        }
      ]
    },
    {
      "name": "Scent",
      "item": [
        {
          "name": "Offers",
          "item": [
            {
              "name": "List your own offers",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/offers",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "offers"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "disabled": true
                    },
                    {
                      "key": "pageSize",
                      "value": "20",
                      "disabled": true
                    },
                    {
                      "key": "status",
                      "value": "",
                      "description": "Filter to a single status.",
                      "disabled": true
                    }
                  ]
                },
                "description": "**List your own offers**\n\nReturns a paged list of the calling vendor's own offers (any submission source), optionally filtered by status. Requires the `Offers` capability."
              },
              "response": []
            },
            {
              "name": "Submit a new offer",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/offers",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "offers"
                  ]
                },
                "description": "**Submit a new offer**\n\nSubmits a new offer through the SAME review workflow the Portal vendor UI uses — nothing\ngoes live without an internal reviewer's approval. Idempotent when `vendorExternalId` is\nset: re-submitting the SAME `vendorExternalId` returns the already-created offer unchanged\nrather than creating a duplicate or erroring (a safe retry). Supply `variantId` for a\nvendor-proposed match to an existing catalog variant, or `rawBrandName`/`rawProductName`\nfor an unmatched free-form entry. Requires the `Offers` capability.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"variantId\": \"00000000-0000-0000-0000-000000000000\",\n  \"vendorSku\": \"\",\n  \"vendorExternalId\": \"\",\n  \"cost\": 0,\n  \"costIsVatInclusive\": false,\n  \"declaredStockQuantity\": 0,\n  \"declaredInStock\": false,\n  \"rawBrandName\": \"\",\n  \"rawProductName\": \"\",\n  \"rawConcentration\": \"\",\n  \"rawSizeMl\": 0,\n  \"rawDescription\": \"\",\n  \"saveAsDraft\": false\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "Get one of your own offers",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/offers/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "offers",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                    }
                  ]
                },
                "description": "**Get one of your own offers**\n\nReturns a single offer by ID, scoped to the calling vendor (any submission source). An offer belonging to another vendor is reported as 404 — indistinguishable from a nonexistent one. Requires the `Offers` capability."
              },
              "response": []
            },
            {
              "name": "Update an existing API-submitted offer",
              "request": {
                "method": "PATCH",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/offers/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "offers",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                    }
                  ]
                },
                "description": "**Update an existing API-submitted offer**\n\nA cost change on an Approved offer becomes a pending change awaiting reviewer approval; an\nidentity-fact change on an Approved offer resets it to `PendingReview`. Never changes the\nmatched `variantId` — re-matching is a reviewer-only action, and never carries declared stock\n— push stock via `PATCH /offers/{offerId}/stock` instead. Reaches ONLY an offer THIS vendor\nsubmitted through THIS door (`source: Api`) — one belonging to another vendor, or one a\ncatalog admin entered manually on this vendor's behalf, is reported as 404. Requires the\n`Offers` capability — prefer `PATCH /offers/{id}/cost` for a cost-only push from a credential\nholding only the `Cost` capability.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"vendorSku\": \"\",\n  \"vendorExternalId\": \"\",\n  \"cost\": 0,\n  \"costIsVatInclusive\": false,\n  \"rawBrandName\": \"\",\n  \"rawProductName\": \"\",\n  \"rawConcentration\": \"\",\n  \"rawSizeMl\": 0,\n  \"rawDescription\": \"\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            }
          ]
        },
        {
          "name": "Cost",
          "item": [
            {
              "name": "Push a cost change for one of your own offers",
              "request": {
                "method": "PATCH",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/offers/:id/cost",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "offers",
                    ":id",
                    "cost"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                    }
                  ]
                },
                "description": "**Push a cost change for one of your own offers**\n\nALWAYS routes through the existing approval pipeline — no new money governance. On an\nApproved offer this sets a pending cost; the live cost stays untouched until an internal\nreviewer applies or discards it. On an offer never yet approved, cost is set directly\n(nothing is live yet regardless — approval is what publishes an offer, not this field).\nEvery OTHER field is left exactly as it was — this endpoint can only ever change cost.\nIdempotent: re-pushing the identical cost overwrites the same pending value, never creates\na duplicate pending artifact. Requires the `Cost` capability — grantable independently of\n`Offers`.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"cost\": 0,\n  \"costIsVatInclusive\": false\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            }
          ]
        },
        {
          "name": "Stock",
          "item": [
            {
              "name": "Push declared stock for one of your own offers",
              "request": {
                "method": "PATCH",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/offers/:offerId/stock",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "offers",
                    ":offerId",
                    "stock"
                  ],
                  "variable": [
                    {
                      "key": "offerId",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                    }
                  ]
                },
                "description": "**Push declared stock for one of your own offers**\n\nApplies availability/quantity INSTANTLY (no review gate) — on an Approved offer this\npropagates immediately to the linked supply option's stock snapshot. Reachable for ANY of\nthe vendor's own offers regardless of submission source — stock is a real-world fact about\nthe vendor's own SKU, independent of which door the offer's identity/cost entered through.\nA declared \"in stock: false\" with a stale positive quantity is still treated as\nunavailable. Idempotent: re-pushing the identical declared state is a pure no-op. Requires\nthe `Stock` capability — grantable independently of `Offers`/`Cost`. Moved here from the\nretired `PATCH /api/v1/scent/stock/{offerId}` door (ApiDocsReview Phase V2) — declared stock\nis now a sub-resource of the offer it declares availability for.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"declaredStockQuantity\": 0,\n  \"declaredInStock\": false\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            }
          ]
        },
        {
          "name": "Orders",
          "item": [
            {
              "name": "List the shipments assigned to you",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/orders",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "orders"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "disabled": true
                    },
                    {
                      "key": "pageSize",
                      "value": "20",
                      "disabled": true
                    },
                    {
                      "key": "status",
                      "value": "",
                      "description": "Repeatable match-any filter over fulfilment status (?status=Shipped&status=Delivered). Omit to include every status.",
                      "disabled": true
                    },
                    {
                      "key": "changedSince",
                      "value": "2026-01-01T00:00:00Z",
                      "description": "Only return rows whose parent order changed strictly after this UTC instant (delta sync).",
                      "disabled": true
                    }
                  ]
                },
                "description": "**List the shipments assigned to you**\n\nReturns a paged, cost-stripped list of your own **fulfilment groups** — the API's term for\nthe shipment/parcel assigned to you (`groupId` in the response). Never a sibling vendor's\nshipment, Linra's own Self-fulfilled one, or an order's OTHER vendors' lines. No partner\nidentity, order totals, commission, or any money field is ever present (see the schema's own\nfield-by-field remarks). `status` is a repeatable match-any filter\n(`?status=Shipped&status=Delivered`) — a single occurrence still works; omit it entirely to\ninclude every status. Set `changedSince` for delta sync: only groups whose PARENT ORDER\nchanged strictly after that instant are returned, ordered by change time ascending. A sibling\nvendor's own group changing on the SAME order also bumps this timestamp — treat a repeat of\nyour own unchanged group as a safe, over-inclusive signal, never under-inclusive, and\nreconcile idempotently. Requires the `Orders` capability."
              },
              "response": []
            },
            {
              "name": "Get one shipment assigned to you",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/orders/:groupId",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "orders",
                    ":groupId"
                  ],
                  "variable": [
                    {
                      "key": "groupId",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                    }
                  ]
                },
                "description": "**Get one shipment assigned to you**\n\nReturns a single fulfilment group by ID, scoped to the calling vendor. A group belonging to another vendor is reported as 404. Requires the `Orders` capability."
              },
              "response": []
            },
            {
              "name": "Update a shipment's status",
              "request": {
                "method": "PATCH",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/orders/:groupId/status",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "orders",
                    ":groupId",
                    "status"
                  ],
                  "variable": [
                    {
                      "key": "groupId",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                    }
                  ]
                },
                "description": "**Update a shipment's status**\n\nVendors may push ONLY `Shipped`, `Delivered`, or `Cancelled` (from `Pending`/`Shipped`) —\nforward-only. `Packed` and any backward/stale/invalid target are rejected as a clean\nno-op (HTTP 200, `applied: false`, the group's UNCHANGED current state) — never an error,\nso your automation can distinguish \"my push landed\" from \"the group was already past that\npoint.\" Re-pushing the identical, already-applied status is also a no-op. Requires the\n`Orders` capability.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"status\": \"Pending\",\n  \"trackingRef\": \"\",\n  \"carrier\": \"Aramex\",\n  \"carrierNameOther\": \"\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            }
          ]
        },
        {
          "name": "Returns",
          "item": [
            {
              "name": "Confirm receipt of your own returned item",
              "request": {
                "method": "PATCH",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/returns/:id/received",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "returns",
                    ":id",
                    "received"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                      "description": "The return request id."
                    }
                  ]
                },
                "description": "**Confirm receipt of your own returned item**\n\nModel-A supplier door: `InTransit` -> `Received`. Reaches ONLY a return request whose\ndestination is your own vendor id (`ReturnDestinationType == Supplier`) — any other\nrequest 404s. Rejects a request not currently `InTransit` with\n`BUSINESS_RETURN_REQUEST_INVALID_TRANSITION`. Requires the `Orders` capability."
              },
              "response": []
            },
            {
              "name": "Start inspecting your own returned item",
              "request": {
                "method": "PATCH",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/returns/:id/inspection/start",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "returns",
                    ":id",
                    "inspection",
                    "start"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                      "description": "The return request id."
                    }
                  ]
                },
                "description": "**Start inspecting your own returned item**\n\nModel-A supplier door: `Received` -> `UnderInspection`. Same ownership scope as\n`markVendorReturnReceived`. Rejects a request not currently `Received` with\n`BUSINESS_RETURN_REQUEST_INVALID_TRANSITION`. Requires the `Orders` capability."
              },
              "response": []
            },
            {
              "name": "Upload inspection evidence (a damage photo or document)",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/returns/:id/evidence",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "returns",
                    ":id",
                    "evidence"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                      "description": "The return request id."
                    }
                  ]
                },
                "description": "**Upload inspection evidence (a damage photo or document)**\n\nUploads one piece of evidence against your own return request's inspection. At least one\n`Photo` must exist before `inspection/propose` accepts a proposal. Requires the request to\nalready be `Received` (`BUSINESS_RETURN_REQUEST_NOT_RECEIVED` otherwise). Same ownership\nscope as `markVendorReturnReceived`. Requires the `Orders` capability.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"url\": \"https://example.com/webhook\",\n  \"kind\": \"Photo\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "Propose an inspection outcome (never authoritative)",
              "request": {
                "method": "PATCH",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/returns/:id/inspection/propose",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "returns",
                    ":id",
                    "inspection",
                    "propose"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                      "description": "The return request id."
                    }
                  ]
                },
                "description": "**Propose an inspection outcome (never authoritative)**\n\nRecords your own PROPOSED outcome — Linra's assessment is always decisive; this never\ntransitions the request's status or triggers execution. A proposed `Rejected` flags the\nrequest for staff review. Rejected with `BUSINESS_RETURN_INSPECTION_EVIDENCE_REQUIRED`\nunless at least one photo has already been uploaded via `addVendorReturnEvidence`.\nRequires the request to currently be `UnderInspection`\n(`BUSINESS_RETURN_REQUEST_INVALID_TRANSITION` otherwise). Same ownership scope as\n`markVendorReturnReceived`. Requires the `Orders` capability.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"outcome\": \"AcceptedDefect\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            }
          ]
        },
        {
          "name": "Webhooks",
          "item": [
            {
              "name": "List your own webhook subscriptions",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/webhooks",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "webhooks"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "disabled": true
                    },
                    {
                      "key": "pageSize",
                      "value": "20",
                      "disabled": true
                    }
                  ]
                },
                "description": "**List your own webhook subscriptions**\n\nLists every webhook subscription owned by the calling vendor. Never returns a secret. No `activeOnly` filter — the subscription cap is small and you benefit from seeing inactive ones too. No capability required — ownership is the only gate."
              },
              "response": []
            },
            {
              "name": "Create a webhook subscription",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/webhooks",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "webhooks"
                  ]
                },
                "description": "**Create a webhook subscription**\n\nRegisters a new webhook endpoint for the calling vendor. The signing secret is\nserver-generated and returned in THIS response ONLY — it is never shown again. Subject to a\nper-vendor active-subscription cap (see the Webhooks & HMAC verification guide). The\ncapability required is NOT fixed — it is checked per requested event type, against that\nevent type's own natural owner (see the event catalogue below): you need `Orders` to\nsubscribe to `vendor.scent.order.assigned` and `Offers` to subscribe to\n`vendor.scent.offer.review-decision`, independently.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"url\": \"https://example.com/webhook\",\n  \"eventTypes\": [\n    \"vendor.scent.order.assigned\"\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "Get a webhook subscription by id",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/webhooks/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "webhooks",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                    }
                  ]
                },
                "description": "**Get a webhook subscription by id**\n\nRetrieves one of the calling vendor's own webhook subscriptions. Never returns a secret. No capability required — ownership is the only gate."
              },
              "response": []
            },
            {
              "name": "Update a webhook subscription's URL and/or event types",
              "request": {
                "method": "PUT",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/webhooks/:id",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "webhooks",
                    ":id"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                    }
                  ]
                },
                "description": "**Update a webhook subscription's URL and/or event types**\n\nEither field may be omitted to leave it unchanged. A changed URL is re-validated against the\nSSRF guardrails. Adding a new event type is checked against THAT event type's own required\ncapability (same rule as create) — never a single fixed capability for the whole call.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"url\": \"https://example.com/webhook\",\n  \"eventTypes\": [\n    \"vendor.scent.order.assigned\"\n  ]\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "Activate or deactivate a webhook subscription",
              "request": {
                "method": "PATCH",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/webhooks/:id/active",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "webhooks",
                    ":id",
                    "active"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                    }
                  ]
                },
                "description": "**Activate or deactivate a webhook subscription**\n\nAn inactive subscription receives no deliveries and does not count toward the active-subscription cap. No capability required — ownership is the only gate.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"isActive\": false\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "Rotate a webhook subscription's signing secret",
              "request": {
                "method": "POST",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/webhooks/:id/rotate-secret",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "webhooks",
                    ":id",
                    "rotate-secret"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                    }
                  ]
                },
                "description": "**Rotate a webhook subscription's signing secret**\n\nGenerates a new server-side signing secret and returns it in THIS response ONLY — the previous secret stops verifying signatures immediately. No capability required — ownership is the only gate."
              },
              "response": []
            },
            {
              "name": "Send a test delivery for a webhook subscription",
              "request": {
                "method": "POST",
                "header": [
                  {
                    "key": "Content-Type",
                    "value": "application/json"
                  }
                ],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/webhooks/:id/test-fire",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "webhooks",
                    ":id",
                    "test-fire"
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                    }
                  ]
                },
                "description": "**Send a test delivery for a webhook subscription**\n\nSynthesizes a sample payload for the given (already-subscribed) event type and dispatches it through the SAME delivery path — including HMAC signing and the SSRF guardrails — a real event would use. No capability required — ownership is the only gate.",
                "body": {
                  "mode": "raw",
                  "raw": "{\n  \"eventType\": \"vendor.scent.order.assigned\"\n}",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  }
                }
              },
              "response": []
            },
            {
              "name": "List delivery attempts for a webhook subscription",
              "request": {
                "method": "GET",
                "header": [],
                "url": {
                  "raw": "{{baseUrl}}/api/v1/scent/webhooks/:id/deliveries",
                  "host": [
                    "{{baseUrl}}"
                  ],
                  "path": [
                    "api",
                    "v1",
                    "scent",
                    "webhooks",
                    ":id",
                    "deliveries"
                  ],
                  "query": [
                    {
                      "key": "page",
                      "value": "1",
                      "disabled": true
                    },
                    {
                      "key": "pageSize",
                      "value": "20",
                      "disabled": true
                    }
                  ],
                  "variable": [
                    {
                      "key": "id",
                      "value": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                    }
                  ]
                },
                "description": "**List delivery attempts for a webhook subscription**\n\nSelf-serve debugging read — every delivery ATTEMPT (not just distinct logical events) within the configured retention window, newest first. No capability required — ownership is the only gate."
              },
              "response": []
            }
          ]
        }
      ]
    }
  ]
}
