{"components":{"parameters":{"DateFromParam":{"in":"query","name":"dateFrom","schema":{"format":"date","type":"string"}},"DateToParam":{"in":"query","name":"dateTo","schema":{"format":"date","type":"string"}},"IdempotencyKey":{"description":"Client-generated UUID. Required on every state-changing POST; a request-hash mismatch on replay returns 409.","in":"header","name":"Idempotency-Key","required":true,"schema":{"format":"uuid","type":"string"}},"PageParam":{"in":"query","name":"page","schema":{"default":1,"minimum":1,"type":"integer"}},"PageSizeParam":{"in":"query","name":"pageSize","schema":{"default":25,"maximum":200,"minimum":1,"type":"integer"}},"SortByParam":{"description":"camelCase field name.","in":"query","name":"sortBy","schema":{"type":"string"}},"SortOrderParam":{"in":"query","name":"sortOrder","schema":{"default":"desc","enum":["asc","desc"],"type":"string"}}},"responses":{"BadRequest":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Validation failure (bad-request, invalid-vessel-assignments, role-not-allowed-for-tenant-type, *-permissions-reserved)."},"Conflict":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"State conflict (quote-expired, order-status-conflict, rfq-status-conflict, quote-status-conflict, quote-accepted-pending, delivery-status-conflict, menu-plan-status-conflict, stock-take-status-conflict, stock-receipt-exists, client-billing-paused, idempotency request-hash mismatch)."},"Forbidden":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Missing permission or disallowed scope (forbidden, scope-not-supported, spot-not-supported, vessel-not-assigned, sanctions-blocked)."},"IntakeUnavailable":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Intake temporarily unavailable (intake-unavailable) — the Turnstile verifier is unreachable and the surface fails closed; retriable."},"InternalError":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Unhandled server error (internal-error)."},"NotFound":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Resource does not exist or is out of tenant scope (not-found)."},"RevokeUnavailable":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The identity provider refused or failed to terminate the session (revoke-unavailable) — nothing was revoked; a connected AI application stays live, a browser session stays signed in. Retriable."},"SessionsUnavailable":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"The identity provider could not be read, so the user's sessions are unknown right now. Retriable."},"TooManyRequests":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Rate limited. On the public intake ops: rate-limited (per-IP burst or the global daily intake ceiling). On mutating ops: write-quota-exceeded — a write quota is exhausted; the message names the scope (actor-burst | staff-burst | tenant-daily | fanout-daily) and the UTC reset, and the response carries Retry-After. Per-surface codes (challenge-cooldown on the webhook challenge resend) keep their own semantics."},"VerificationUnavailable":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Email verification temporarily unavailable (verification-unavailable) — the API has no verification key configured, so no token can be checked; the link itself may still be valid. Retriable."}},"schemas":{"AcceptClientAgreement":{"properties":{"textSha256":{"description":"SHA-256 (lowercase hex) of the text the person read, computed by the portal over the text it rendered. Must equal that version's published hash (else 409 client-agreement-text-mismatch).","pattern":"^[0-9a-f]{64}$","type":"string"},"version":{"description":"Must equal agreement.currentVersion (else 409 client-agreement-version-mismatch).","maxLength":40,"minLength":1,"type":"string"}},"required":["version","textSha256"],"type":"object"},"ActorContext":{"properties":{"dataScope":{"enum":["all-vessels","assigned-vessels","own-orders","pass-through"],"type":"string"},"permissionKeys":{"items":{"type":"string"},"type":"array"},"roles":{"items":{"type":"string"},"type":"array"},"tenantId":{"$ref":"#/components/schemas/Uuid"},"tenantType":{"enum":["contracted","spot","nermo","vendor"],"type":"string"},"userId":{"$ref":"#/components/schemas/Uuid"}},"required":["userId","tenantId","tenantType","roles","permissionKeys","dataScope"],"type":"object"},"AdminAcceptClientAgreement":{"properties":{"reference":{"description":"The written instrument the acceptance rests on — recorded as the acceptance reference.","maxLength":200,"minLength":1,"type":"string"},"version":{"description":"Must equal agreement.currentVersion (else 409 client-agreement-version-mismatch).","maxLength":40,"minLength":1,"type":"string"}},"required":["version","reference"],"type":"object"},"AdminCanonicalProduct":{"properties":{"allergens":{"items":{"type":"string"},"type":"array"},"categoryId":{"$ref":"#/components/schemas/Uuid"},"dietaryTags":{"items":{"type":"string"},"type":"array"},"embeddingModel":{"description":"The model that embedded the current name; null = unembedded (the created-but-unembedded trap, made visible).","nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"impaCode":{"type":"string"},"name":{"type":"string"},"source":{"enum":["impa","nermo"],"type":"string"},"standardUnitCode":{"type":"string"},"status":{"enum":["active","deprecated"],"type":"string"},"synonymCount":{"minimum":0,"type":"integer"}},"required":["id","name","source","status","embeddingModel","synonymCount"],"type":"object"},"AdminCanonicalProductDetail":{"properties":{"allergens":{"items":{"type":"string"},"type":"array"},"categoryId":{"$ref":"#/components/schemas/Uuid"},"categoryPath":{"description":"The category chain root→leaf; empty for an uncategorized row.","items":{"$ref":"#/components/schemas/CategoryPathNode"},"type":"array"},"dietaryTags":{"items":{"type":"string"},"type":"array"},"embeddingModel":{"description":"null = unembedded.","nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"impaCode":{"type":"string"},"mappingCount":{"description":"T23 mappings targeting this canonical (all review states).","minimum":0,"type":"integer"},"name":{"type":"string"},"pendingObservationCount":{"description":"T25 pending rows matching this canonical's norms or proposing onto it.","minimum":0,"type":"integer"},"promotedVariantCount":{"description":"T62 promoted rows resolved onto this canonical (directly or via a synonym).","minimum":0,"type":"integer"},"source":{"enum":["impa","nermo"],"type":"string"},"standardUnitCode":{"type":"string"},"status":{"enum":["active","deprecated"],"type":"string"},"synonymCount":{"minimum":0,"type":"integer"}},"required":["id","name","source","status","embeddingModel","categoryPath","synonymCount","promotedVariantCount","mappingCount","pendingObservationCount"],"type":"object"},"AdminCanonicalProductList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/AdminCanonicalProduct"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"AdminComplianceDocument":{"allOf":[{"$ref":"#/components/schemas/ComplianceDocument"},{"properties":{"vendorId":{"$ref":"#/components/schemas/Uuid"},"vendorName":{"type":"string"},"verifiedByActor":{"format":"uuid","nullable":true,"type":"string"}},"required":["vendorId","vendorName"],"type":"object"}]},"AdminComplianceDocumentList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/AdminComplianceDocument"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"AdminCreateClientVessel":{"description":"The staff twin of CreateVessel — the acceptance is the reference to the client's written instrument.","properties":{"callSign":{"type":"string"},"flagCountry":{"maxLength":3,"minLength":3,"type":"string"},"imo":{"description":"Optional; check-digit validated server-side (handler guard). Budgets require an IMO.","pattern":"^[0-9]{7}$","type":"string"},"name":{"maxLength":200,"minLength":1,"type":"string"},"subscriptionAcceptance":{"$ref":"#/components/schemas/StaffSubscriptionAcceptance"},"vesselType":{"enum":["barge","bulk-carrier","container-ship","cruise","drillship","ferry","fpso","lng-carrier","lpg-carrier","offshore","ro-ro","tanker","tug"],"type":"string"}},"required":["name","subscriptionAcceptance"],"type":"object"},"AdminOrderList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/AdminOrderSummary"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"AdminOrderSummary":{"description":"Staff cross-tenant order list row — the order header plus both counterparties (the owning client tenant and the vendor).","properties":{"createdAt":{"$ref":"#/components/schemas/Timestamp"},"currency":{"$ref":"#/components/schemas/CurrencyCode"},"id":{"$ref":"#/components/schemas/Uuid"},"reference":{"type":"string"},"status":{"enum":["awaiting-vendor-acceptance","confirmed","delivered","invoiced","cancelled","declined","retracted"],"type":"string"},"tenantId":{"$ref":"#/components/schemas/Uuid"},"tenantName":{"type":"string"},"totalAmount":{"$ref":"#/components/schemas/Money"},"vendorId":{"$ref":"#/components/schemas/Uuid"},"vendorName":{"type":"string"}},"required":["id","reference","tenantId","tenantName","vendorId","vendorName","status","currency","createdAt"],"type":"object"},"AdminProductCategory":{"properties":{"code":{"type":"string"},"deprecatedCount":{"minimum":0,"type":"integer"},"id":{"$ref":"#/components/schemas/Uuid"},"name":{"type":"string"},"parentId":{"format":"uuid","nullable":true,"type":"string"},"productCount":{"minimum":0,"type":"integer"}},"required":["id","code","name","parentId","productCount","deprecatedCount"],"type":"object"},"AdminProductCategoryList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/AdminProductCategory"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"AdminReactivateClientVessel":{"description":"The staff reactivate body — also the only way a declined vessel returns (a fresh pending acceptance).","properties":{"subscriptionAcceptance":{"$ref":"#/components/schemas/StaffSubscriptionAcceptance"}},"required":["subscriptionAcceptance"],"type":"object"},"AdminRfqDetail":{"description":"Staff cross-tenant RFQ detail — the client-facing RFQ projection wrapped with its owning tenant. The wrapper exists because Rfq itself is single-tenant (client-owned) and carries no tenant attribution; quote drafts stay excluded, as on every non-owning-vendor read.","properties":{"rfq":{"$ref":"#/components/schemas/Rfq"},"tenantId":{"$ref":"#/components/schemas/Uuid"},"tenantName":{"type":"string"}},"required":["tenantId","tenantName","rfq"],"type":"object"},"AdminRfqList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/AdminRfqSummary"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"AdminRfqSummary":{"description":"Staff cross-tenant RFQ list row — the RFQ header plus its owning client tenant and a line count.","properties":{"createdAt":{"$ref":"#/components/schemas/Timestamp"},"eta":{"format":"date-time","nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"lineCount":{"minimum":0,"type":"integer"},"port":{"$ref":"#/components/schemas/PortRef"},"portId":{"$ref":"#/components/schemas/Uuid"},"reference":{"type":"string"},"status":{"enum":["draft","sent","replies-received","awarded","closed","cancelled"],"type":"string"},"tenantId":{"$ref":"#/components/schemas/Uuid"},"tenantName":{"type":"string"},"vesselId":{"$ref":"#/components/schemas/Uuid"}},"required":["id","reference","tenantId","tenantName","vesselId","portId","port","status","lineCount","createdAt"],"type":"object"},"AdminUpdateClientVessel":{"description":"Staff metadata patch — UpdateVessel plus the in-place IMO correction.","minProperties":1,"properties":{"callSign":{"type":"string"},"flagCountry":{"maxLength":3,"minLength":3,"type":"string"},"imo":{"pattern":"^[0-9]{7}$","type":"string"},"name":{"maxLength":200,"minLength":1,"type":"string"},"vesselType":{"enum":["barge","bulk-carrier","container-ship","cruise","drillship","ferry","fpso","lng-carrier","lpg-carrier","offshore","ro-ro","tanker","tug"],"type":"string"}},"type":"object"},"AdminVendorCatalogItem":{"properties":{"active":{"type":"boolean"},"availability":{"enum":["in-stock","limited","out-of-stock"],"nullable":true,"type":"string"},"canonicalProductId":{"format":"uuid","nullable":true,"type":"string"},"canonicalProductName":{"nullable":true,"type":"string"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"description":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"images":{"description":"Read-only gallery metadata (bytes ride the staff non-contract serving route).","items":{"$ref":"#/components/schemas/VendorCatalogItemImage"},"type":"array"},"leadTimeDays":{"nullable":true,"type":"integer"},"name":{"type":"string"},"packQty":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"},"packUnitCode":{"nullable":true,"type":"string"},"priceUpdatedAt":{"description":"When the unit price was last set; null = no price recorded yet. The freshness input the rebate ladder counts.","format":"date-time","nullable":true,"type":"string"},"stockQty":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"},"unitCode":{"type":"string"},"unitPrice":{"$ref":"#/components/schemas/DecimalString"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"},"vendorSku":{"nullable":true,"type":"string"}},"required":["id","name","unitCode","unitPrice","active","images","createdAt","updatedAt","priceUpdatedAt"],"type":"object"},"AdminVendorCatalogItemList":{"properties":{"currency":{"$ref":"#/components/schemas/VendorCurrency"},"data":{"items":{"$ref":"#/components/schemas/AdminVendorCatalogItem"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"},"summary":{"$ref":"#/components/schemas/AdminVendorCatalogSummary"}},"required":["data","pagination","currency","summary"],"type":"object"},"AdminVendorCatalogSummary":{"properties":{"activeItems":{"minimum":0,"type":"integer"},"freshItems":{"description":"Active rows whose price was set within freshWithinDays.","minimum":0,"type":"integer"},"freshWithinDays":{"description":"The freshness window the sweep applies, returned so the label never drifts from the rule.","minimum":1,"type":"integer"},"linkedItems":{"description":"Active rows with a canonical link; unmapped = activeItems - linkedItems.","minimum":0,"type":"integer"},"stockTrackedItems":{"description":"Active rows with a tracked stockQty.","minimum":0,"type":"integer"},"totalItems":{"description":"All rows, active or not.","minimum":0,"type":"integer"}},"required":["totalItems","activeItems","linkedItems","freshItems","stockTrackedItems","freshWithinDays"],"type":"object"},"AdminVendorPaymentMethod":{"allOf":[{"$ref":"#/components/schemas/VendorPaymentMethod"},{"properties":{"vendorId":{"$ref":"#/components/schemas/Uuid"},"vendorName":{"type":"string"}},"required":["vendorId","vendorName"],"type":"object"}],"description":"A vendor payment method on the staff surface — the masked row plus the owning vendor (the cross-vendor queue's link target)."},"AdminVendorPaymentMethodList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/AdminVendorPaymentMethod"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"AdminWebhookDeliveryList":{"description":"Same retention bounds as the tenant-scoped history (settled 30d, dead-lettered 90d).","properties":{"data":{"items":{"$ref":"#/components/schemas/WebhookDelivery"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"AdminWebhookSubscription":{"description":"Staff cross-tenant projection — only nermo_staff_ro-granted columns, never signing material.","properties":{"active":{"type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"eventTypes":{"items":{"type":"string"},"type":"array"},"health":{"$ref":"#/components/schemas/AdminWebhookSubscriptionHealth"},"id":{"$ref":"#/components/schemas/Uuid"},"label":{"maxLength":200,"nullable":true,"type":"string"},"rotationOpen":{"type":"boolean"},"secretRotatedAt":{"format":"date-time","nullable":true,"type":"string"},"tenantId":{"$ref":"#/components/schemas/Uuid"},"tenantName":{"type":"string"},"tenantType":{"description":"tenants.tenant_type — contracted | spot | vendor.","type":"string"},"url":{"type":"string"},"verifiedAt":{"format":"date-time","nullable":true,"type":"string"}},"required":["id","tenantId","tenantName","tenantType","url","eventTypes","active","rotationOpen","createdAt","health"],"type":"object"},"AdminWebhookSubscriptionHealth":{"description":"Delivery-health rollup for the staff lane. Counts are bounded by the delivery retention windows (settled 30d, dead-lettered 90d), so they are honest against pruning. pendingDeliveryCount counts undelivered scheduled rows — including never-attempted first deliveries, hence \"deliveries\", not \"retries\". The breaker pair surfaces the circuit-breaker state: crossing the failure threshold over a sustained failing-since window auto-deactivates the endpoint.\n","properties":{"consecutiveFailures":{"description":"The breaker's counter — failed attempts since the last success, reset on any success.","minimum":0,"type":"integer"},"deadLetteredCount":{"minimum":0,"type":"integer"},"failingSince":{"description":"Start of the current failure run; null when the endpoint is not failing.","format":"date-time","nullable":true,"type":"string"},"lastAttemptAt":{"format":"date-time","nullable":true,"type":"string"},"lastAttemptStatus":{"nullable":true,"type":"integer"},"pendingDeliveryCount":{"minimum":0,"type":"integer"}},"required":["deadLetteredCount","pendingDeliveryCount","consecutiveFailures"],"type":"object"},"AdminWebhookSubscriptionList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/AdminWebhookSubscription"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"AgreementReleaseOutcome":{"description":"What one Agreement acceptance did to the vessels the activation gate was holding. Accepting is a fan-out, not a single-vessel act, and each held vessel flips on its own savepoint, so one acceptance can end in all three outcomes at once. A vessel that left pending between the read and the flip is counted in none. A replay of an acceptance already on record reports zeros.","properties":{"activated":{"description":"Vessels this acceptance activated — each one's subscription started and its invoice issued.","minimum":0,"type":"integer"},"awaitingCheck":{"description":"Vessels whose registration check has not cleared them yet. They activate on their own when it finishes — nothing for anyone to do.","minimum":0,"type":"integer"},"needsReview":{"description":"Vessels that could not activate because their IMO is already held by an active vessel. NERMO staff resolve the duplicate; they stay pending until then.","minimum":0,"type":"integer"}},"required":["activated","needsReview","awaitingCheck"],"type":"object"},"Allowance":{"description":"A standing daily allowance — a provisions product the crew gets regardless of the menu. per-person-day expands as qtyPerDay × crew complement × plan days; per-vessel-day as qtyPerDay × plan days.","properties":{"basis":{"enum":["per-person-day","per-vessel-day"],"type":"string"},"canonicalProductId":{"$ref":"#/components/schemas/Uuid"},"id":{"$ref":"#/components/schemas/Uuid"},"notes":{"nullable":true,"type":"string"},"productName":{"type":"string"},"productStatus":{"enum":["active","deprecated"],"type":"string"},"qtyPerDay":{"$ref":"#/components/schemas/DecimalString"},"unitCode":{"type":"string"}},"required":["id","canonicalProductId","productName","productStatus","basis","qtyPerDay","unitCode"],"type":"object"},"AllowanceInput":{"properties":{"basis":{"enum":["per-person-day","per-vessel-day"],"type":"string"},"canonicalProductId":{"$ref":"#/components/schemas/Uuid"},"notes":{"maxLength":500,"type":"string"},"qtyPerDay":{"$ref":"#/components/schemas/DecimalString"},"unitCode":{"minLength":1,"type":"string"}},"required":["canonicalProductId","basis","qtyPerDay","unitCode"],"type":"object"},"AllowanceList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Allowance"},"type":"array"}},"required":["data"],"type":"object"},"ApplicationContact":{"properties":{"email":{"description":"Becomes the future admin-user identity; availability is checked by the workflow risk gate, never at intake.","minLength":3,"type":"string"},"name":{"minLength":1,"type":"string"},"phone":{"type":"string"},"role":{"minLength":1,"type":"string"}},"required":["name","role","email"],"type":"object"},"ApplicationOwner":{"description":"Beneficial owner / director — lands ONLY in kyc_parties (encrypted + blind-indexed), never in the application snapshot.","properties":{"dob":{"format":"date","type":"string"},"name":{"minLength":1,"type":"string"},"nationality":{"description":"ISO 3166-1 alpha-3.","maxLength":3,"minLength":3,"type":"string"},"role":{"enum":["beneficial-owner","director","signatory","authorized-contact"],"type":"string"}},"required":["name","role"],"type":"object"},"ApplicationReceipt":{"properties":{"reference":{"description":"High-entropy tracking reference (NER-A-/NER-V- + 16 Crockford-base32 chars). The only key the public status read accepts.","type":"string"},"status":{"$ref":"#/components/schemas/ApplicationStatus"}},"required":["reference","status"],"type":"object"},"ApplicationStatus":{"description":"Coarse public status (the get_application_status mapping). All vendor pre-decision states read in-review.","enum":["received","in-review","action-required","approved","declined"],"type":"string"},"ApplicationTerms":{"properties":{"accepted":{"description":"Must be true — the acceptance gate is a handler guard (false → 400).","type":"boolean"},"version":{"minLength":1,"type":"string"}},"required":["version","accepted"],"type":"object"},"AuditEvent":{"properties":{"action":{"enum":["insert","update","delete","accessed","exported"],"type":"string"},"actorId":{"description":"users.id or service_accounts.id (per actor_type); null for system / break-glass actors.","nullable":true,"type":"string"},"changedColumns":{"items":{"type":"string"},"type":"array"},"id":{"$ref":"#/components/schemas/Uuid"},"occurredAt":{"$ref":"#/components/schemas/Timestamp"},"rowId":{"nullable":true,"type":"string"},"tableName":{"type":"string"},"tenantId":{"$ref":"#/components/schemas/Uuid"}},"required":["id","action","tableName","changedColumns","occurredAt"],"type":"object"},"AuditEventList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/AuditEvent"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"BulkUpsertVendorCatalogItemOutcome":{"properties":{"id":{"$ref":"#/components/schemas/Uuid"},"outcome":{"enum":["created","updated","unchanged"],"type":"string"}},"required":["id","outcome"],"type":"object"},"BulkUpsertVendorCatalogItems":{"properties":{"deactivateMissing":{"default":false,"type":"boolean"},"items":{"items":{"$ref":"#/components/schemas/VendorCatalogItemInput"},"maxItems":4000,"minItems":1,"type":"array"}},"required":["items"],"type":"object"},"BulkUpsertVendorCatalogItemsResult":{"properties":{"deactivated":{"minimum":0,"type":"integer"},"results":{"items":{"$ref":"#/components/schemas/BulkUpsertVendorCatalogItemOutcome"},"type":"array"}},"required":["results","deactivated"],"type":"object"},"CanonicalProduct":{"properties":{"allergens":{"items":{"type":"string"},"type":"array"},"categoryId":{"$ref":"#/components/schemas/Uuid"},"dietaryTags":{"items":{"type":"string"},"type":"array"},"id":{"$ref":"#/components/schemas/Uuid"},"impaCode":{"type":"string"},"name":{"type":"string"},"source":{"enum":["impa","nermo"],"type":"string"},"standardUnitCode":{"type":"string"}},"required":["id","name","source"],"type":"object"},"CanonicalProductList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/CanonicalProduct"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"CanonicalProductSubstitute":{"properties":{"allergens":{"items":{"type":"string"},"type":"array"},"canonicalProductId":{"$ref":"#/components/schemas/Uuid"},"dietaryTags":{"items":{"type":"string"},"type":"array"},"impaCode":{"type":"string"},"name":{"type":"string"},"rank":{"minimum":1,"type":"integer"},"scope":{"$ref":"#/components/schemas/SubstituteScope"},"standardUnitCode":{"type":"string"}},"required":["canonicalProductId","name","rank","scope","allergens","dietaryTags"],"type":"object"},"CanonicalProductSubstituteList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/CanonicalProductSubstitute"},"type":"array"}},"required":["data"],"type":"object"},"CategoryPathNode":{"properties":{"code":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"name":{"type":"string"}},"required":["id","code","name"],"type":"object"},"CateringBudget":{"properties":{"crewCount":{"type":"integer"},"effectiveFrom":{"format":"date","type":"string"},"mandayRate":{"$ref":"#/components/schemas/Money"},"mandayRateUsd":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"}},"required":["mandayRate","mandayRateUsd","crewCount","effectiveFrom"],"type":"object"},"CateringGroup":{"properties":{"code":{"type":"string"},"name":{"type":"string"},"share":{"$ref":"#/components/schemas/DecimalString"},"usd":{"$ref":"#/components/schemas/DecimalString"}},"required":["code","name","usd","share"],"type":"object"},"CateringMonth":{"properties":{"budgetMandayUsd":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"},"byGroup":{"items":{"$ref":"#/components/schemas/CateringGroup"},"type":"array"},"crewCount":{"nullable":true,"type":"integer"},"days":{"type":"integer"},"deliveredUsd":{"$ref":"#/components/schemas/DecimalString"},"deliveries":{"type":"integer"},"mandayUsd":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"},"month":{"pattern":"^\\d{4}-\\d{2}$","type":"string"}},"required":["month","days","crewCount","deliveries","deliveredUsd","mandayUsd","budgetMandayUsd","byGroup"],"type":"object"},"CateringReport":{"properties":{"budget":{"$ref":"#/components/schemas/CateringBudget"},"byGroup":{"items":{"$ref":"#/components/schemas/CateringGroup"},"type":"array"},"months":{"items":{"$ref":"#/components/schemas/CateringMonth"},"type":"array"},"unconvertedLines":{"type":"integer"},"vesselId":{"$ref":"#/components/schemas/Uuid"},"vesselImo":{"nullable":true,"type":"string"},"vesselName":{"type":"string"}},"required":["vesselId","vesselName","vesselImo","months","byGroup","unconvertedLines"],"type":"object"},"ClientAgreementAcceptance":{"properties":{"acceptedAt":{"$ref":"#/components/schemas/Timestamp"},"acceptedBy":{"format":"uuid","nullable":true,"type":"string"},"released":{"allOf":[{"$ref":"#/components/schemas/AgreementReleaseOutcome"}],"description":"What this acceptance released. Both doors carry it."},"version":{"type":"string"}},"required":["version","acceptedAt","released"],"type":"object"},"ClientAgreementStatus":{"description":"CLIENT ONLY. The Client Agreement acceptance state: the version the platform currently presents, and the acceptance on record (absent fields = never accepted). The first vessel add requires acceptedVersion == currentVersion (else 409 client-agreement-required).","properties":{"acceptedAt":{"format":"date-time","nullable":true,"type":"string"},"acceptedBy":{"description":"The accepting actor's user id.","format":"uuid","nullable":true,"type":"string"},"acceptedByEmail":{"description":"The accepting user's email when the actor is a user of the tenant.","nullable":true,"type":"string"},"acceptedTextSha256":{"description":"SHA-256 (lowercase hex) of the exact Agreement text the acceptance rests on — the published text of acceptedVersion. Null on acceptances recorded before the text was published per version.","nullable":true,"type":"string"},"acceptedVersion":{"nullable":true,"type":"string"},"currentVersion":{"type":"string"},"reference":{"description":"Staff source only — the written instrument.","nullable":true,"type":"string"},"source":{"description":"The door the acceptance came through: portal (a person in the Client Portal) or staff (a written instrument NERMO recorded). mcp appears only on acceptances recorded before agent acceptance was withdrawn; no new one is ever written.","enum":["portal","mcp","staff"],"nullable":true,"type":"string"},"vesselsAwaitingAgreement":{"description":"How many of the tenant's pending vessels the activation gate is holding for want of an executed Agreement. Accepting activates them and invoices each, so both acceptance doors state the consequence before the click. Zero once any version is in force.","minimum":0,"type":"integer"}},"required":["currentVersion","vesselsAwaitingAgreement"],"type":"object"},"ClientAgreementText":{"description":"CLIENT ONLY. The published text of one Client Agreement version.","properties":{"text":{"description":"The Agreement as Markdown: headings, paragraphs, emphasis, lists and tables.","type":"string"},"textSha256":{"description":"SHA-256 (lowercase hex) of the UTF-8 bytes of text.","pattern":"^[0-9a-f]{64}$","type":"string"},"version":{"type":"string"}},"required":["version","textSha256","text"],"type":"object"},"ClientApplicationRequest":{"additionalProperties":false,"properties":{"company":{"properties":{"city":{"minLength":1,"type":"string"},"country":{"minLength":1,"type":"string"},"legalEntityType":{"type":"string"},"legalName":{"minLength":1,"type":"string"},"registeredAddress":{"minLength":1,"type":"string"},"registrationNumber":{"minLength":1,"type":"string"},"vatNumber":{"type":"string"},"website":{"type":"string"}},"required":["legalName","country","registeredAddress","city","registrationNumber"],"type":"object"},"contact":{"$ref":"#/components/schemas/ApplicationContact"},"fleet":{"description":"Scoping/coverage signal only (feeds vendor-coverage planning, not screening).","properties":{"keyPorts":{"description":"UN/LOCODEs of the fleet's key calling ports (picked, not typed).","items":{"maxLength":5,"minLength":5,"pattern":"^[A-Z0-9]{5}$","type":"string"},"type":"array"},"mcpAgentIntent":{"type":"boolean"},"procurementMethod":{"type":"string"},"regions":{"items":{"type":"string"},"type":"array"},"size":{"type":"string"},"targetStart":{"type":"string"},"vesselTypes":{"items":{"type":"string"},"type":"array"}},"type":"object"},"owners":{"items":{"$ref":"#/components/schemas/ApplicationOwner"},"minItems":1,"type":"array"},"renderedAt":{"description":"Unix millis the form rendered — the server-side min-fill-time check's input.","format":"int64","type":"integer"},"terms":{"$ref":"#/components/schemas/ApplicationTerms"},"turnstileToken":{"description":"Cloudflare Turnstile response token. Absent only under the API's affirmative dev bypass.","type":"string"},"website":{"description":"Honeypot — must be empty.","maxLength":0,"type":"string"}},"required":["terms","company","contact","owners","renderedAt"],"type":"object"},"ClientDetail":{"properties":{"agreement":{"allOf":[{"$ref":"#/components/schemas/ClientAgreementStatus"}],"description":"The Client Agreement acceptance on record, with the hash of the text accepted — the staff evidence surface. Present on the detail read only (absent on list rows)."},"billingStatus":{"description":"The F-5 dunning state — paused blocks NEW RFQ origination only; in-flight procurement concludes. Flips back on payment or staff receipt.","enum":["active","paused"],"type":"string"},"city":{"nullable":true,"type":"string"},"contractEndedAt":{"description":"Set when the client contract is ended — gates every billing scan; existing debt and dunning are untouched.","format":"date-time","nullable":true,"type":"string"},"country":{"nullable":true,"type":"string"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"emergencyContactName":{"nullable":true,"type":"string"},"emergencyContactPhone":{"nullable":true,"type":"string"},"legalEntityType":{"nullable":true,"type":"string"},"missingBudgetCount":{"description":"ACTIVE vessels in this tenant with no current budget row (non-active vessels can't originate RFQs, so they aren't drift).","minimum":0,"type":"integer"},"name":{"type":"string"},"phone":{"nullable":true,"type":"string"},"registeredAddress":{"nullable":true,"type":"string"},"registrationNumber":{"nullable":true,"type":"string"},"status":{"enum":["active","deactivated"],"type":"string"},"tenantId":{"$ref":"#/components/schemas/Uuid"},"tenantType":{"enum":["contracted","spot"],"type":"string"},"tradingName":{"nullable":true,"type":"string"},"userCount":{"minimum":0,"type":"integer"},"vatNumber":{"nullable":true,"type":"string"},"vesselCount":{"minimum":0,"type":"integer"},"website":{"nullable":true,"type":"string"}},"required":["tenantId","name","tenantType","status","userCount","vesselCount","missingBudgetCount","createdAt"],"type":"object"},"ClientList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ClientDetail"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"ClientRef":{"description":"Nested read reference to a client tenant (no CRUD surface in this cut).","properties":{"name":{"type":"string"},"tenantId":{"$ref":"#/components/schemas/Uuid"},"tenantType":{"enum":["contracted","spot"],"type":"string"}},"required":["tenantId","name","tenantType"],"type":"object"},"ClientSubscription":{"properties":{"createdAt":{"$ref":"#/components/schemas/Timestamp"},"effectiveFrom":{"format":"date","type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"monthlyRatePerVessel":{"$ref":"#/components/schemas/Money"}},"required":["id","monthlyRatePerVessel","effectiveFrom","createdAt"],"type":"object"},"ClientSubscriptionList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ClientSubscription"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"ClientTermsOverride":{"description":"Per-client override row (T90). Vendor-only surface — clients never see this shape.","properties":{"clientName":{"nullable":true,"type":"string"},"clientTenantId":{"$ref":"#/components/schemas/Uuid"},"terms":{"$ref":"#/components/schemas/PaymentTerms"},"updatedAt":{"format":"date-time","type":"string"}},"required":["clientTenantId","terms"],"type":"object"},"ClientTermsOverrideList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/ClientTermsOverride"},"type":"array"}},"required":["items"],"type":"object"},"CompanyBilling":{"description":"CLIENT ONLY. Contract facts. Everything here is read-only EXCEPT entityName/email/address. The subscription RATE is not a clients column — the Billing tab reads it from GET /finance/subscription; the invoice + payment ledger stays at /finance.","properties":{"address":{"nullable":true,"type":"string"},"billingStatus":{"description":"The F-5 dunning state — paused blocks NEW RFQ origination only.","enum":["active","paused"],"type":"string"},"contractCurrency":{"$ref":"#/components/schemas/CurrencyCode"},"contractEndedAt":{"format":"date-time","nullable":true,"type":"string"},"contractType":{"enum":["provision","technical","both"],"nullable":true,"type":"string"},"email":{"nullable":true,"type":"string"},"entityName":{"nullable":true,"type":"string"}},"required":["contractCurrency","billingStatus"],"type":"object"},"CompanyDirector":{"properties":{"name":{"description":"Decrypted from kyc_parties.name_enc. dob_enc/nationality_enc are NEVER projected.","type":"string"},"role":{"enum":["beneficial-owner","director","signatory","authorized-contact"],"type":"string"}},"required":["role","name"],"type":"object"},"CompanyDirectorList":{"description":"`directors` is OPTIONAL by design — its ABSENCE is the degradation discriminator when the PII codec is nil (NERMO_PII_KEYS unset) or a decrypt fails. Never [] (which would assert \"none recorded\") and never a 500 (which would take the surface down over an optional block).","properties":{"directors":{"items":{"$ref":"#/components/schemas/CompanyDirector"},"type":"array"}},"type":"object"},"CompanyIdentity":{"description":"Class I — staff-maintained. Read-only on this surface (\"contact NERMO to change\").","properties":{"city":{"nullable":true,"type":"string"},"country":{"description":"ISO 3166-1 alpha-3 in practice — both public intake forms send countryToAlpha3(input) and reject an unresolvable country. Staff-provisioned rows and direct API callers are unconstrained, so this is deliberately NOT format-constrained; render through alpha3ToCountry with a raw-string fallback.","nullable":true,"type":"string"},"legalEntityType":{"nullable":true,"type":"string"},"legalName":{"description":"The registered legal entity. Vendor: vendors.legal_name. Client: PROJECTED read-only from tenants.name (there is no clients.legal_name column — a second truth).","type":"string"},"registeredAddress":{"nullable":true,"type":"string"},"registrationNumber":{"nullable":true,"type":"string"},"vatNumber":{"nullable":true,"type":"string"}},"required":["legalName"],"type":"object"},"CompanyPresentation":{"description":"Class P — tenant-editable (admin.company.manage). The union of both parties' keys; the vendor-only ones are absent from a client response and rejected by the handler's party guard on a client write.","properties":{"displayName":{"description":"VENDOR ONLY. The short public label — what /suppliers/{slug} and the client-facing directory render. Vendor-authored (ruled 2026-07-28); a change also syncs tenants.name, the anchor the VendorRef/ClientRef counterparty projections read.","nullable":true,"type":"string"},"email":{"description":"VENDOR ONLY. Company general address (clients carry billing_email instead).","nullable":true,"type":"string"},"emergencyContactName":{"description":"Out-of-hours contact.","nullable":true,"type":"string"},"emergencyContactPhone":{"nullable":true,"type":"string"},"employeeCount":{"description":"VENDOR ONLY.","minimum":0,"nullable":true,"type":"integer"},"parentCompany":{"description":"VENDOR ONLY.","nullable":true,"type":"string"},"phone":{"description":"Company general line — distinct from any user's.","nullable":true,"type":"string"},"productServiceScope":{"description":"VENDOR ONLY.","nullable":true,"type":"string"},"taxOffice":{"description":"VENDOR ONLY.","nullable":true,"type":"string"},"tradingName":{"description":"The full \"trading as\" name from the registration form; portal-internal.","nullable":true,"type":"string"},"website":{"nullable":true,"type":"string"},"yearEstablished":{"description":"VENDOR ONLY.","maximum":2100,"minimum":1800,"nullable":true,"type":"integer"}},"type":"object"},"CompanyProfile":{"properties":{"agreement":{"$ref":"#/components/schemas/ClientAgreementStatus"},"billing":{"$ref":"#/components/schemas/CompanyBilling"},"defaultCurrency":{"allOf":[{"$ref":"#/components/schemas/VendorCurrency"}],"description":"VENDOR ONLY (absent for a client): the vendor's goods currency — prices the whole catalogue and is the default quote / order currency. Editable through UpdateCompanyProfile."},"identity":{"$ref":"#/components/schemas/CompanyIdentity"},"party":{"enum":["client","vendor"],"type":"string"},"presentation":{"$ref":"#/components/schemas/CompanyPresentation"}},"required":["party","identity","presentation"],"type":"object"},"ComplianceDocStatus":{"enum":["pending-review","verified","rejected","expiring","expired"],"type":"string"},"ComplianceDocType":{"enum":["business-registration","tax-certificate","insurance","food-safety-haccp","customs-broker-license","eco-certification","kyc-identity","dangerous-goods-authorization","port-security-credential","launch-barge-declaration","asbestos-free-declaration","ihm-declaration","tanker-trade-statement","other"],"type":"string"},"ComplianceDocument":{"properties":{"contentType":{"nullable":true,"type":"string"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"docType":{"$ref":"#/components/schemas/ComplianceDocType"},"expiresAt":{"format":"date","nullable":true,"type":"string"},"fileName":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"issuedAt":{"format":"date","nullable":true,"type":"string"},"notes":{"nullable":true,"type":"string"},"status":{"$ref":"#/components/schemas/ComplianceDocStatus"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"},"verifiedAt":{"format":"date-time","nullable":true,"type":"string"}},"required":["id","docType","status","createdAt","updatedAt"],"type":"object"},"ComplianceDocumentList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ComplianceDocument"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"ConfirmDelivery":{"properties":{"lines":{"items":{"$ref":"#/components/schemas/ConfirmDeliveryLine"},"maxItems":500,"minItems":1,"type":"array"},"podDocumentRef":{"type":"string"}},"required":["lines"],"type":"object"},"ConfirmDeliveryLine":{"properties":{"lineStatus":{"enum":["ok","short","subst","damaged"],"type":"string"},"notes":{"type":"string"},"orderLineId":{"$ref":"#/components/schemas/Uuid"},"qtyReceived":{"$ref":"#/components/schemas/DecimalString"}},"required":["orderLineId","qtyReceived","lineStatus"],"type":"object"},"CostCategory":{"description":"A product domain — a vendor-suppliable cost category and the catalogue's shelf axis (catalog § cost_category).","enum":["provisions","bonded-stores","technical-stores","medical-stores","spare-parts","services","safety-stores","cabin-stores","cleaning-stores","slop-chest","stationery","lubricants"],"type":"string"},"CounterpartyBar":{"properties":{"clientTenantId":{"format":"uuid","nullable":true,"type":"string"},"createdAt":{"format":"date-time","type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"liftedReason":{"nullable":true,"type":"string"},"reason":{"type":"string"},"scope":{"enum":["vessel","company"],"type":"string"},"status":{"enum":["active","lifted"],"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"vesselId":{"format":"uuid","nullable":true,"type":"string"},"vesselImo":{"nullable":true,"type":"string"}},"required":["id","scope","reason","status","createdAt","updatedAt"],"type":"object"},"CounterpartyBarInput":{"properties":{"clientTenantId":{"format":"uuid","type":"string"},"reason":{"minLength":1,"type":"string"},"scope":{"enum":["vessel","company"],"type":"string"},"vesselId":{"format":"uuid","type":"string"},"vesselImo":{"type":"string"}},"required":["scope","reason"],"type":"object"},"CounterpartyBarLift":{"properties":{"liftedReason":{"minLength":1,"type":"string"}},"required":["liftedReason"],"type":"object"},"CounterpartyFlag":{"properties":{"category":{"enum":["non-payment","operational","conduct","other"],"type":"string"},"createdAt":{"format":"date-time","type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"incidentCount":{"type":"integer"},"level":{"enum":["hard-block","warn"],"type":"string"},"note":{"nullable":true,"type":"string"},"status":{"enum":["active","withdrawn"],"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"vesselImo":{"nullable":true,"type":"string"},"vesselName":{"type":"string"}},"required":["id","vesselName","category","level","status","incidentCount","createdAt","updatedAt"],"type":"object"},"CounterpartyFlagDetail":{"allOf":[{"$ref":"#/components/schemas/CounterpartyFlag"},{"properties":{"incidents":{"items":{"$ref":"#/components/schemas/CounterpartyFlagIncident"},"type":"array"}},"required":["incidents"],"type":"object"}]},"CounterpartyFlagIncident":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"note":{"type":"string"},"occurredAt":{"format":"date-time","type":"string"},"orderId":{"format":"uuid","nullable":true,"type":"string"},"orderReference":{"nullable":true,"type":"string"}},"required":["id","note","occurredAt","createdAt"],"type":"object"},"CounterpartyFlagIncidentInput":{"properties":{"note":{"minLength":1,"type":"string"},"occurredAt":{"format":"date-time","type":"string"},"orderId":{"format":"uuid","type":"string"}},"required":["note"],"type":"object"},"CounterpartyFlagInput":{"properties":{"category":{"enum":["non-payment","operational","conduct","other"],"type":"string"},"dispatchId":{"format":"uuid","type":"string"},"level":{"enum":["hard-block","warn"],"type":"string"},"note":{"type":"string"},"orderId":{"format":"uuid","type":"string"}},"required":["category","level"],"type":"object"},"CounterpartyFlagUpdate":{"properties":{"category":{"enum":["non-payment","operational","conduct","other"],"type":"string"},"level":{"enum":["hard-block","warn"],"type":"string"},"note":{"nullable":true,"type":"string"},"status":{"enum":["active","withdrawn"],"type":"string"}},"type":"object"},"CoveredPorts":{"properties":{"unlocodes":{"items":{"maxLength":5,"minLength":5,"type":"string"},"type":"array"}},"required":["unlocodes"],"type":"object"},"CreateCanonicalProduct":{"properties":{"allergens":{"items":{"type":"string"},"type":"array"},"categoryId":{"$ref":"#/components/schemas/Uuid"},"dietaryTags":{"items":{"type":"string"},"type":"array"},"name":{"maxLength":200,"minLength":1,"type":"string"},"standardUnitCode":{"maxLength":16,"type":"string"}},"required":["name","categoryId","standardUnitCode"],"type":"object"},"CreateClient":{"description":"The contract currency is not an input: NERMO bills subscriptions in USD only (founder ruling 2026-09-04), so the server fixes clients.contract_currency to USD.","properties":{"name":{"type":"string"},"tenantType":{"enum":["contracted","spot"],"type":"string"}},"required":["name","tenantType"],"type":"object"},"CreateClientSubscription":{"properties":{"effectiveFrom":{"format":"date","type":"string"},"monthlyRatePerVessel":{"$ref":"#/components/schemas/Money"}},"required":["monthlyRatePerVessel","effectiveFrom"],"type":"object"},"CreateMenuPlan":{"description":"fromMenuPlanId copies the source's slots + crew_complement + duration_days (a point-in-time snapshot); body fields override. Allowances — a vessel-bound plan copies the vessel's standing list (the source's only when the vessel has none); a vessel-less plan copies the source's, or starts with none. isTemplate=true drops vessel/dates (templates have neither).","properties":{"crewComplement":{"minimum":1,"type":"integer"},"durationDays":{"minimum":1,"type":"integer"},"fromMenuPlanId":{"format":"uuid","type":"string"},"isTemplate":{"type":"boolean"},"name":{"minLength":1,"type":"string"},"startDate":{"format":"date","type":"string"},"vesselId":{"format":"uuid","type":"string"}},"required":["name"],"type":"object"},"CreateMenuTemplate":{"properties":{"allowances":{"items":{"$ref":"#/components/schemas/AllowanceInput"},"maxItems":200,"type":"array"},"crewProfile":{"$ref":"#/components/schemas/CrewProfile"},"durationDays":{"default":7,"maximum":60,"minimum":1,"type":"integer"},"name":{"minLength":1,"type":"string"},"slots":{"items":{"$ref":"#/components/schemas/SetMenuPlanSlot"},"maxItems":500,"type":"array"}},"required":["name"],"type":"object"},"CreateOrder":{"properties":{"quoteId":{"$ref":"#/components/schemas/Uuid"}},"required":["quoteId"],"type":"object"},"CreateProductCategory":{"properties":{"code":{"maxLength":64,"minLength":1,"type":"string"},"name":{"maxLength":200,"minLength":1,"type":"string"},"parentId":{"$ref":"#/components/schemas/Uuid"}},"required":["code","name","parentId"],"type":"object"},"CreateRecipe":{"description":"forkFromRecipeId records provenance (editing a shared corpus recipe = fork); the body is authoritative — the portal pre-fills it from the source. New recipes are always drafts.","properties":{"allergens":{"items":{"enum":["celery","crustaceans","eggs","fish","gluten","lupin","milk","molluscs","mustard","nuts","peanuts","sesame","soybeans","sulphites"],"type":"string"},"type":"array"},"category":{"type":"string"},"cookMin":{"minimum":0,"type":"integer"},"cuisine":{"type":"string"},"dietaryTags":{"items":{"enum":["halal","kosher","vegetarian","vegan","pescatarian","no-pork","no-beef"],"type":"string"},"type":"array"},"forkFromRecipeId":{"format":"uuid","type":"string"},"ingredients":{"items":{"$ref":"#/components/schemas/CreateRecipeIngredient"},"maxItems":200,"minItems":1,"type":"array"},"method":{"type":"string"},"name":{"minLength":1,"type":"string"},"prepMin":{"minimum":0,"type":"integer"},"serves":{"minimum":1,"type":"integer"}},"required":["name","serves","ingredients"],"type":"object"},"CreateRecipeIngredient":{"properties":{"canonicalProductId":{"format":"uuid","nullable":true,"type":"string"},"ingredientText":{"minLength":1,"type":"string"},"qty":{"$ref":"#/components/schemas/DecimalString"},"sourceText":{"minLength":1,"type":"string"},"substituteCanonicalProductId":{"format":"uuid","nullable":true,"type":"string"},"substitutionPolicy":{"default":"product-default","enum":["product-default","no-substitute","recipe-alternate"],"type":"string"},"unitCode":{"minLength":1,"type":"string"}},"required":["sourceText","qty","unitCode"],"type":"object"},"CreateRfq":{"properties":{"asDraft":{"description":"Save as a draft (status 'draft', not routed to vendors) instead of sending. Omitted/false sends the RFQ immediately and fans it out to eligible vendors. The MCP request_quote tool never sets this. A sent (non-draft) RFQ must carry at least one line — enforced server-side.","type":"boolean"},"deliveryLocation":{"enum":["berth","anchorage","unknown"],"type":"string"},"eta":{"format":"date-time","type":"string"},"lines":{"items":{"$ref":"#/components/schemas/CreateRfqLine"},"maxItems":500,"type":"array"},"notes":{"type":"string"},"portId":{"$ref":"#/components/schemas/Uuid"},"source":{"enum":["portal","mcp","menu-plan"],"type":"string"},"vendorIds":{"description":"Optional explicit vendor restriction (RFQ targeting): fan out only to this subset ∩ the unchanged routing eligibility predicate. Send path only — combined with asDraft it is a 400 (the restriction is dispatch-instruction state, not RFQ content; drafts carry none — supply it on sendRfq). An unknown / non-active vendor id → 400 vendor-not-targetable; port-coverage eligibility is deliberately not pre-checked (the routing intersect owns it). Omitted = full port fan-out, exactly today's behavior.","items":{"$ref":"#/components/schemas/Uuid"},"maxItems":100,"minItems":1,"type":"array"},"vesselId":{"$ref":"#/components/schemas/Uuid"}},"required":["vesselId","portId","source","deliveryLocation","lines"],"type":"object"},"CreateRfqLine":{"properties":{"canonicalProductId":{"format":"uuid","nullable":true,"type":"string"},"notes":{"type":"string"},"qty":{"$ref":"#/components/schemas/DecimalString"},"sourceText":{"type":"string"},"unitCode":{"type":"string"}},"required":["sourceText","qty","unitCode"],"type":"object"},"CreateRole":{"properties":{"name":{"type":"string"},"permissionKeys":{"items":{"type":"string"},"type":"array"}},"required":["name","permissionKeys"],"type":"object"},"CreateStockTake":{"description":"A draft count. countedAt defaults to today (backdating allowed — a line whose product was counted on a LATER date is skipped as stale at posting).","properties":{"countedAt":{"format":"date","type":"string"},"lines":{"items":{"$ref":"#/components/schemas/StockTakeLineInput"},"maxItems":1000,"type":"array"},"notes":{"maxLength":2000,"type":"string"}},"type":"object"},"CreateUser":{"properties":{"displayName":{"type":"string"},"email":{"format":"email","type":"string"},"roleCode":{"type":"string"},"vesselAssignments":{"items":{"$ref":"#/components/schemas/Uuid"},"type":"array"}},"required":["email","displayName","roleCode"],"type":"object"},"CreateUserOnTenant":{"properties":{"displayName":{"type":"string"},"email":{"format":"email","type":"string"},"roleCode":{"type":"string"},"vesselAssignments":{"items":{"$ref":"#/components/schemas/Uuid"},"type":"array"}},"required":["email","displayName","roleCode"],"type":"object"},"CreateVendor":{"properties":{"defaultCurrency":{"$ref":"#/components/schemas/VendorCurrency"},"displayName":{"type":"string"},"legalName":{"type":"string"},"primaryPortId":{"format":"uuid","nullable":true,"type":"string"},"status":{"default":"active","description":"Initial vendor lifecycle status. Defaults to active (the v0 ops-vouches-at-create model).","enum":["intake","kyc-pending","active","suspended","terminated"],"type":"string"},"trustedFastTrack":{"default":false,"type":"boolean"}},"required":["legalName","displayName","defaultCurrency"],"type":"object"},"CreateVendorPaymentMethodRequest":{"description":"Initial self-service banking entry (the bank-change step-up explicitly does NOT apply here). The IBAN and SWIFT are envelope-encrypted at rest and reads return the last-4 hint only; the account holder name is stored in cleartext and returned on every read.","properties":{"accountHolder":{"description":"The name the account is held in — for a company account, the vendor's registered legal name. Stored in cleartext and shown to the vendor and to NERMO staff wherever the account is shown (it is what staff check against the legal name and the bank letter at verify time).","minLength":1,"type":"string"},"currency":{"$ref":"#/components/schemas/CurrencyCode"},"iban":{"minLength":8,"type":"string"},"isDefault":{"default":true,"type":"boolean"},"swift":{"maxLength":11,"minLength":8,"type":"string"}},"required":["iban","swift","accountHolder","currency"],"type":"object"},"CreateVendorRebateAgreement":{"properties":{"effectiveFrom":{"format":"date","type":"string"},"paymentTermsDays":{"default":14,"minimum":0,"type":"integer"},"ratePct":{"$ref":"#/components/schemas/DecimalString"}},"required":["ratePct","effectiveFrom"],"type":"object"},"CreateVessel":{"properties":{"callSign":{"type":"string"},"flagCountry":{"maxLength":3,"minLength":3,"type":"string"},"imo":{"description":"Optional; check-digit validated server-side (handler guard). Budgets require an IMO.","pattern":"^[0-9]{7}$","type":"string"},"name":{"maxLength":200,"minLength":1,"type":"string"},"subscriptionAcceptance":{"$ref":"#/components/schemas/SubscriptionAcceptance"},"vesselType":{"enum":["barge","bulk-carrier","container-ship","cruise","drillship","ferry","fpso","lng-carrier","lpg-carrier","offshore","ro-ro","tanker","tug"],"type":"string"}},"required":["name","subscriptionAcceptance"],"type":"object"},"CreateVesselBudget":{"properties":{"crewCount":{"minimum":1,"type":"integer"},"effectiveFrom":{"format":"date","type":"string"},"mandayRate":{"description":"The vessel's day-rate. A budget may be in any supported goods currency (VendorCurrency), independent of the client's contract_currency (which denominates NERMO's own receivables). Every non-cancelled order confirmed in the month counts against the budget whatever its currency: the budget sweep converts each one into the budget currency at the reference rate for the order's confirmation date.","properties":{"amount":{"$ref":"#/components/schemas/DecimalString"},"currency":{"$ref":"#/components/schemas/VendorCurrency"}},"required":["amount","currency"],"type":"object"},"vesselImo":{"type":"string"}},"required":["vesselImo","effectiveFrom","mandayRate","crewCount"],"type":"object"},"CreateVesselGroup":{"properties":{"name":{"type":"string"},"vesselIds":{"items":{"$ref":"#/components/schemas/Uuid"},"type":"array"}},"required":["name"],"type":"object"},"CreatedUserWithTempPassword":{"properties":{"temporaryPassword":{"description":"One-time temporary password. Present once on create; null on an idempotent replay.","type":"string"},"user":{"$ref":"#/components/schemas/User"}},"required":["user"],"type":"object"},"CreditAllocation":{"description":"One application of a credit note to an invoice (derived bookkeeping — released if the invoice is voided).","properties":{"amount":{"$ref":"#/components/schemas/Money"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"creditNoteId":{"$ref":"#/components/schemas/Uuid"},"reason":{"description":"The credit note's reason.","type":"string"}},"required":["creditNoteId","amount","reason","createdAt"],"type":"object"},"CreditNote":{"description":"Carry-forward-only subscription credit (decommission proration or staff goodwill) — applied to invoices, never cashed out. Currency is server-set from the credited invoice.","properties":{"amount":{"$ref":"#/components/schemas/Money"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"currency":{"$ref":"#/components/schemas/CurrencyCode"},"id":{"$ref":"#/components/schemas/Uuid"},"invoiceId":{"$ref":"#/components/schemas/Uuid"},"reason":{"type":"string"},"remainingAmount":{"$ref":"#/components/schemas/Money"},"vesselId":{"description":"Set on decommission credits only.","format":"uuid","nullable":true,"type":"string"}},"required":["id","invoiceId","amount","currency","reason","remainingAmount","createdAt"],"type":"object"},"CrewProfile":{"enum":["filipino","indian","chinese","indonesian","ukrainian-eastern-european","western-northern-european","south-american"],"type":"string"},"CurrencyCode":{"description":"ISO-4217 currency code.","example":"USD","pattern":"^[A-Z]{3}$","type":"string"},"DecideUnitAlias":{"properties":{"action":{"enum":["approve","stoplist"],"type":"string"},"alias":{"maxLength":64,"minLength":1,"type":"string"},"unitCode":{"maxLength":16,"type":"string"}},"required":["alias","action"],"type":"object"},"DecimalString":{"description":"Exact decimal serialized as a string (never a JSON float).","example":"1234.56","pattern":"^-?\\d+(\\.\\d+)?$","type":"string"},"DeclineOrder":{"properties":{"note":{"nullable":true,"type":"string"},"reason":{"enum":["out-of-stock","cannot-make-eta","flagged-counterparty","other"],"type":"string"}},"required":["reason"],"type":"object"},"DeclineRfqDispatch":{"properties":{"note":{"type":"string"},"reason":{"enum":["out-of-stock","cannot-make-eta","outside-coverage","flagged-counterparty","other"],"type":"string"}},"required":["reason"],"type":"object"},"DecommissionVessel":{"description":"Staff decommission body. effectiveAt must be strictly future (past/present → 400); omitted = immediate. Re-POST on an already-scheduled vessel overwrites (reschedule).","properties":{"effectiveAt":{"format":"date-time","type":"string"}},"type":"object"},"Delivery":{"properties":{"autoConcludeAt":{"format":"date-time","nullable":true,"type":"string"},"client":{"$ref":"#/components/schemas/ClientRef"},"clientTenantId":{"$ref":"#/components/schemas/Uuid"},"confirmedByName":{"description":"Display name of the confirming user. Party-scoped by RLS at the read layer: populated for the client's own scope (and staff), null for the vendor side — the client user's identity is never disclosed across the intermediation boundary.","nullable":true,"type":"string"},"confirmedByUserId":{"format":"uuid","nullable":true,"type":"string"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"deliveredAt":{"format":"date-time","nullable":true,"type":"string"},"feedback":{"description":"The client's own post-delivery vendor-feedback submissions (detail read only). NERMO-internal — never projected to the vendor side.","items":{"$ref":"#/components/schemas/VendorFeedback"},"type":"array"},"id":{"$ref":"#/components/schemas/Uuid"},"lines":{"items":{"$ref":"#/components/schemas/DeliveryLine"},"type":"array"},"locationType":{"enum":["berth","anchorage"],"type":"string"},"orderId":{"$ref":"#/components/schemas/Uuid"},"orderReference":{"type":"string"},"podDocumentRef":{"nullable":true,"type":"string"},"port":{"$ref":"#/components/schemas/PortRef"},"portId":{"$ref":"#/components/schemas/Uuid"},"reference":{"type":"string"},"scheduledAt":{"format":"date-time","nullable":true,"type":"string"},"status":{"enum":["scheduled","in-transit","delivered","exception"],"type":"string"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"},"vendorPodDocumentRef":{"nullable":true,"type":"string"},"vendorReportNote":{"nullable":true,"type":"string"},"vendorReportedDeliveredAt":{"format":"date-time","nullable":true,"type":"string"},"vendorTenantId":{"$ref":"#/components/schemas/Uuid"},"vessel":{"$ref":"#/components/schemas/VesselRef"},"vesselId":{"$ref":"#/components/schemas/Uuid"}},"required":["id","reference","clientTenantId","vendorTenantId","orderId","orderReference","vesselId","portId","status","lines","createdAt","updatedAt"],"type":"object"},"DeliveryLine":{"properties":{"canonicalProductId":{"format":"uuid","nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"lineStatus":{"enum":["ok","short","subst","damaged"],"type":"string"},"notes":{"nullable":true,"type":"string"},"orderLineId":{"$ref":"#/components/schemas/Uuid"},"qtyOrdered":{"$ref":"#/components/schemas/DecimalString"},"qtyReceived":{"$ref":"#/components/schemas/DecimalString"},"sourceText":{"type":"string"}},"required":["id","orderLineId","sourceText","qtyOrdered","qtyReceived","lineStatus"],"type":"object"},"DeliveryList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Delivery"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"DeprecateCanonicalProduct":{"properties":{"keepNameAsSynonym":{"default":false,"type":"boolean"},"mergeIntoCanonicalProductId":{"$ref":"#/components/schemas/Uuid"}},"type":"object"},"DeprecateCanonicalProductResult":{"properties":{"nameSynonymAdded":{"type":"boolean"},"pendingProposalsCleared":{"minimum":0,"type":"integer"},"source":{"$ref":"#/components/schemas/CanonicalProduct"},"substituteEdgesDropped":{"minimum":0,"type":"integer"},"substituteEdgesRepointed":{"minimum":0,"type":"integer"},"synonymsMoved":{"minimum":0,"type":"integer"},"target":{"$ref":"#/components/schemas/CanonicalProduct"}},"required":["source","synonymsMoved","nameSynonymAdded","pendingProposalsCleared"],"type":"object"},"EmailVerificationRequest":{"properties":{"token":{"description":"The signed verification token minted into the onboarding-email-verify mail. Opaque to the caller.","maxLength":512,"minLength":16,"type":"string"}},"required":["token"],"type":"object"},"EndClientContract":{"properties":{"reason":{"minLength":1,"type":"string"}},"required":["reason"],"type":"object"},"Error":{"properties":{"error":{"description":"Canonical kebab-case code (catalog § api_error_codes).","example":"not-found","type":"string"},"message":{"description":"Human-readable detail; never leaks internals on 5xx.","type":"string"},"requestId":{"$ref":"#/components/schemas/Uuid"}},"required":["error"],"type":"object"},"GenerateRfqFromMenuPlan":{"description":"portId is required (rfqs.port_id is NOT NULL — every draft carries a port, editable in the workbench). vesselId is required exactly when the plan is vessel-less; a value conflicting with a vessel-bound plan rejects. The RFQ carries no currency — the vendor quotes in its own and the winning quote's currency governs the order.","properties":{"bufferPct":{"default":0,"maximum":50,"minimum":0,"type":"integer"},"deliveryLocation":{"default":"unknown","enum":["berth","anchorage","unknown"],"type":"string"},"eta":{"format":"date-time","type":"string"},"netStockOnBoard":{"default":true,"type":"boolean"},"portId":{"$ref":"#/components/schemas/Uuid"},"vesselId":{"format":"uuid","type":"string"}},"required":["portId"],"type":"object"},"GenerateSubscriptionInvoice":{"properties":{"period":{"description":"The period start — must be the first day of a UTC calendar month.","format":"date","type":"string"}},"required":["period"],"type":"object"},"ImportCandidate":{"properties":{"batchRef":{"type":"string"},"conversionFactor":{"description":"Exact decimal; absent for the factor-1.0 common case.","type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"kind":{"enum":["canonical","variant"],"type":"string"},"minedFromCanonical":{"description":"The source corpus's canonical name — the family identity (absent on kind=canonical head rows).","type":"string"},"promotedRef":{"type":"string"},"proposedCanonicalProductId":{"$ref":"#/components/schemas/Uuid"},"proposedCanonicalProductImpaCode":{"description":"Joined IMPA code of the proposal target — distinguishes same-name IMPA pack/spec twins; absent for code-less NERMO-authored targets.","type":"string"},"proposedCanonicalProductName":{"description":"Joined display name for the proposal target.","type":"string"},"proposedConfidence":{"description":"Engine cosine similarity as exact decimal; absent = exact-dedupe hit (or no proposal yet).","type":"string"},"sourceText":{"type":"string"},"sourceTextNorm":{"type":"string"},"sourceUnitCode":{"type":"string"},"status":{"enum":["pending","promoted","discarded"],"type":"string"},"variantCount":{"type":"integer"}},"required":["id","batchRef","kind","sourceText","sourceTextNorm","variantCount","status"],"type":"object"},"ImportCandidateList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ImportCandidate"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"ImportVariantEvidence":{"properties":{"id":{"$ref":"#/components/schemas/Uuid"},"reviewedByActor":{"$ref":"#/components/schemas/Uuid"},"sourceText":{"type":"string"},"updatedAt":{"format":"date-time","type":"string"},"variantCount":{"type":"integer"}},"required":["id","sourceText","variantCount","updatedAt"],"type":"object"},"ImportVariantEvidenceList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ImportVariantEvidence"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"Invoice":{"properties":{"allocations":{"description":"Present on the detail reads (getInvoice / adminGetInvoice) only.","items":{"$ref":"#/components/schemas/InvoiceAllocation"},"type":"array"},"billingReason":{"enum":["period","vessel-add","reactivation"],"type":"string"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"credits":{"description":"Credit-note applications to this invoice. Present on the detail reads (getInvoice / adminGetInvoice) only.","items":{"$ref":"#/components/schemas/CreditAllocation"},"type":"array"},"currency":{"$ref":"#/components/schemas/CurrencyCode"},"dueAt":{"format":"date-time","nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"invoiceNumber":{"description":"Human reference (NER-INV-{seq}) for dunning/remittance.","type":"string"},"invoiceType":{"enum":["subscription","vendor-rebate"],"type":"string"},"issuedAt":{"$ref":"#/components/schemas/Timestamp"},"issuer":{"$ref":"#/components/schemas/InvoiceIssuer"},"lines":{"description":"Present on the detail reads (getInvoice / adminGetInvoice) only.","items":{"$ref":"#/components/schemas/InvoiceLine"},"type":"array"},"overdue":{"description":"Derived — due date passed while unpaid. Never a status value.","type":"boolean"},"overdueAt":{"description":"Once-only stamp when the overdue predicate first held (the dunning anchor).","format":"date-time","nullable":true,"type":"string"},"periodEnd":{"description":"Half-open — the boundary instant belongs to the next period.","format":"date","type":"string"},"periodStart":{"format":"date","type":"string"},"remittance":{"$ref":"#/components/schemas/InvoiceRemittance"},"status":{"description":"Payment-derived (recomputed from settled allocations, never hand-set). void is terminal — staff-only, excluded from every billing scan.","enum":["due","partially-paid","paid","disputed","void"],"type":"string"},"tenantId":{"description":"The invoiced tenant — meaningful on the staff cross-tenant list (a tenant path always sees its own id).","format":"uuid","type":"string"},"totalAmount":{"$ref":"#/components/schemas/Money"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"},"vesselId":{"description":"Set on add/reactivation stubs only.","format":"uuid","nullable":true,"type":"string"},"voidedAt":{"format":"date-time","nullable":true,"type":"string"}},"required":["id","invoiceNumber","invoiceType","billingReason","periodStart","periodEnd","currency","totalAmount","status","overdue","issuedAt","createdAt","updatedAt","issuer"],"type":"object"},"InvoiceAllocation":{"properties":{"amount":{"$ref":"#/components/schemas/Money"},"method":{"nullable":true,"type":"string"},"paymentId":{"$ref":"#/components/schemas/Uuid"},"paymentStatus":{"enum":["pending","settled","failed","reversed"],"type":"string"},"receivedAt":{"format":"date-time","nullable":true,"type":"string"}},"required":["paymentId","amount","paymentStatus"],"type":"object"},"InvoiceIssuer":{"description":"The issuing legal entity — always Vinterstrom OÜ trading as NERMO (never a per-tenant value). Server-stamped from the billing issuer identity so every rendering surface (portals, invoice PDF) carries the legally required identification from one server-owned source.","properties":{"legalName":{"description":"Registered business name (Vinterstrom OÜ).","type":"string"},"registeredAddress":{"description":"Registered address, single line.","type":"string"},"registryCode":{"description":"Estonian commercial-register code.","type":"string"},"tradeName":{"description":"The name Vinterstrom OÜ trades as (NERMO).","type":"string"},"vatNumber":{"description":"Absent while Vinterstrom OÜ is not VAT-registered; set on registration.","nullable":true,"type":"string"}},"required":["legalName","tradeName","registryCode","registeredAddress"],"type":"object"},"InvoiceLine":{"description":"One flat schema; the parent invoiceType governs which nullable field set is populated (subscription = vessel/days/rate, vendor-rebate = order/value/ratePct).","properties":{"amount":{"$ref":"#/components/schemas/Money"},"billableDays":{"nullable":true,"type":"integer"},"coversFrom":{"description":"subscription line — set only on a short-tail line: the vessel was activated with fewer than ten days left in the previous period, so those days ride this (next) monthly invoice as their own line, covering from this date to that period's end. Null on ordinary full-period lines.","format":"date","nullable":true,"type":"string"},"daysInPeriod":{"nullable":true,"type":"integer"},"fxRate":{"description":"vendor-rebate line on a non-USD order: USD per 1 unit of orderCurrency (8 dp) that amount was converted at — the ECB reference rate for the delivery date, or the fixed USD peg for a pegged currency. Absent on USD orders and on subscription lines.","type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"orderCurrency":{"$ref":"#/components/schemas/CurrencyCode"},"orderId":{"format":"uuid","nullable":true,"type":"string"},"orderReference":{"nullable":true,"type":"string"},"ratePct":{"description":"Decimal fraction as a string (0.0300 = 3%).","nullable":true,"type":"string"},"ratePerVessel":{"$ref":"#/components/schemas/Money"},"subscriptionAcceptanceId":{"description":"subscription line — the recorded fee acceptance (VesselSubscriptionAcceptance on the vessel read) whose days this line bills.","format":"uuid","nullable":true,"type":"string"},"transactionValue":{"$ref":"#/components/schemas/Money"},"vesselId":{"format":"uuid","nullable":true,"type":"string"}},"required":["id","amount"],"type":"object"},"InvoiceList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Invoice"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"InvoiceRemittance":{"description":"Where to pay this invoice — the issuer's receiving bank account for the invoice currency, server-stamped from the billing remittance table (never per-tenant). Absent when no account is on record for the currency; rendering surfaces then say payment details follow separately. Either iban + bic (SEPA/SWIFT) or accountNumber + routingNumber (US) is populated.","properties":{"accountNumber":{"type":"string"},"bankAddress":{"description":"Bank address for the sender, single line.","type":"string"},"bankName":{"type":"string"},"bic":{"description":"SWIFT/BIC.","type":"string"},"iban":{"type":"string"},"payeeName":{"description":"Account holder — the issuing legal entity.","type":"string"},"reference":{"description":"The payment reference to quote — the invoice number.","type":"string"},"routingNumber":{"description":"US ABA routing number.","type":"string"}},"required":["payeeName","bankName","bankAddress","reference"],"type":"object"},"IssueInvoiceCredit":{"properties":{"amount":{"$ref":"#/components/schemas/DecimalString"},"reason":{"minLength":1,"type":"string"}},"required":["amount","reason"],"type":"object"},"MandayCrew":{"properties":{"headcount":{"maximum":500,"minimum":0,"type":"integer"},"label":{"maxLength":80,"type":"string"},"lines":{"items":{"$ref":"#/components/schemas/MandayCrewLine"},"maxItems":200,"type":"array"},"servings":{"items":{"$ref":"#/components/schemas/MandayCrewServing"},"maxItems":200,"type":"array"}},"required":["headcount"],"type":"object"},"MandayCrewLine":{"properties":{"canonicalProductId":{"$ref":"#/components/schemas/Uuid"},"qtyPerCrewDay":{"$ref":"#/components/schemas/DecimalString"},"unitCode":{"maxLength":16,"minLength":1,"type":"string"}},"required":["canonicalProductId","qtyPerCrewDay","unitCode"],"type":"object"},"MandayCrewResult":{"properties":{"headcount":{"type":"integer"},"label":{"type":"string"},"mandayCheapestUsd":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"}},"required":["label","headcount","mandayCheapestUsd"],"type":"object"},"MandayCrewServing":{"properties":{"recipeId":{"$ref":"#/components/schemas/Uuid"},"servingsPerWeek":{"exclusiveMinimum":0,"format":"double","maximum":100,"type":"number"}},"required":["recipeId","servingsPerWeek"],"type":"object"},"MandayEstimate":{"properties":{"blendedCheapestUsd":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"},"blendedMedianUsd":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"},"byGroup":{"items":{"$ref":"#/components/schemas/MandayGroup"},"type":"array"},"crewTotal":{"type":"integer"},"crews":{"items":{"$ref":"#/components/schemas/MandayCrewResult"},"type":"array"},"ports":{"items":{"$ref":"#/components/schemas/MandayPort"},"type":"array"},"prospectFacing":{"type":"boolean"},"rateDate":{"format":"date","nullable":true,"type":"string"},"recipes":{"items":{"$ref":"#/components/schemas/MandayRecipeCost"},"type":"array"},"uncostable":{"$ref":"#/components/schemas/MandayUncostable"},"vendorFloor":{"type":"integer"},"wastePct":{"format":"double","type":"number"},"waterUsdPerCrewDay":{"$ref":"#/components/schemas/DecimalString"}},"required":["rateDate","prospectFacing","vendorFloor","wastePct","crewTotal","blendedCheapestUsd","blendedMedianUsd","waterUsdPerCrewDay","ports","byGroup","crews","recipes","uncostable"],"type":"object"},"MandayEstimateRequest":{"properties":{"crews":{"items":{"$ref":"#/components/schemas/MandayCrew"},"maxItems":20,"minItems":1,"type":"array"},"ports":{"items":{"$ref":"#/components/schemas/MandayPortInput"},"maxItems":60,"minItems":1,"type":"array"},"prospectFacing":{"default":true,"type":"boolean"},"vendorFloor":{"default":3,"maximum":10,"minimum":1,"type":"integer"},"wastePct":{"default":5,"format":"double","maximum":50,"minimum":0,"type":"number"},"water":{"$ref":"#/components/schemas/MandayWater"}},"required":["crews","ports"],"type":"object"},"MandayGroup":{"properties":{"code":{"type":"string"},"name":{"type":"string"},"share":{"$ref":"#/components/schemas/DecimalString"},"usdPerCrewDay":{"$ref":"#/components/schemas/DecimalString"}},"required":["code","name","usdPerCrewDay","share"],"type":"object"},"MandayPort":{"properties":{"byGroup":{"items":{"$ref":"#/components/schemas/MandayGroup"},"type":"array"},"cheapestVendorName":{"nullable":true,"type":"string"},"countryCode":{"maxLength":2,"minLength":2,"type":"string"},"mandayCheapestUsd":{"$ref":"#/components/schemas/DecimalString"},"mandayMedianUsd":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"},"name":{"type":"string"},"oldestPriceAgeDays":{"nullable":true,"type":"integer"},"pricedCostShare":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"},"region":{"nullable":true,"type":"string"},"regionalEstimate":{"type":"boolean"},"supply":{"enum":["covered","below-floor","none"],"type":"string"},"unlocode":{"maxLength":5,"minLength":5,"type":"string"},"vendorsCount":{"nullable":true,"type":"integer"},"weight":{"format":"double","type":"number"}},"required":["unlocode","name","countryCode","region","weight","supply","regionalEstimate","mandayCheapestUsd","pricedCostShare","oldestPriceAgeDays","byGroup"],"type":"object"},"MandayPortInput":{"properties":{"unlocode":{"maxLength":5,"minLength":5,"type":"string"},"weight":{"format":"double","maximum":1000,"minimum":0,"type":"number"}},"required":["unlocode","weight"],"type":"object"},"MandayRecipeCost":{"properties":{"name":{"type":"string"},"recipeId":{"$ref":"#/components/schemas/Uuid"},"usdPerServing":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"}},"required":["recipeId","name","usdPerServing"],"type":"object"},"MandayUncostable":{"properties":{"costableLineShare":{"$ref":"#/components/schemas/DecimalString"},"lines":{"type":"integer"},"unknownRecipeIds":{"items":{"$ref":"#/components/schemas/Uuid"},"type":"array"},"unknownUnlocodes":{"items":{"type":"string"},"type":"array"}},"required":["lines","costableLineShare","unknownRecipeIds","unknownUnlocodes"],"type":"object"},"MandayWater":{"properties":{"canonicalProductId":{"$ref":"#/components/schemas/Uuid"},"litresPerCrewDay":{"format":"double","maximum":20,"minimum":0,"type":"number"}},"required":["canonicalProductId","litresPerCrewDay"],"type":"object"},"MappingEvidence":{"properties":{"confidence":{"description":"Engine cosine similarity as exact decimal; absent for rule/manual rows.","type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"matchSource":{"enum":["rule","engine","manual"],"type":"string"},"reviewState":{"enum":["pending","approved","rejected"],"type":"string"},"sourceText":{"type":"string"},"tenantId":{"$ref":"#/components/schemas/Uuid"},"tenantName":{"type":"string"}},"required":["id","tenantId","tenantName","sourceText","reviewState","matchSource"],"type":"object"},"MappingEvidenceList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/MappingEvidence"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"MappingProposal":{"properties":{"canonicalProductId":{"$ref":"#/components/schemas/Uuid"},"canonicalProductImpaCode":{"description":"Joined IMPA code of the proposed target — distinguishes same-name IMPA pack/spec twins; absent for code-less NERMO-authored targets.","type":"string"},"canonicalProductName":{"type":"string"},"confidence":{"description":"Engine cosine similarity as exact decimal.","type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"sourceText":{"type":"string"},"tenantId":{"$ref":"#/components/schemas/Uuid"},"tenantName":{"type":"string"}},"required":["id","tenantId","tenantName","sourceText","canonicalProductId","canonicalProductName"],"type":"object"},"MappingProposalList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/MappingProposal"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"MappingReviewResult":{"properties":{"action":{"enum":["approve","reject","retarget"],"type":"string"},"canonicalProductId":{"$ref":"#/components/schemas/Uuid"},"mappingId":{"$ref":"#/components/schemas/Uuid"}},"required":["mappingId","action"],"type":"object"},"MatchCandidate":{"properties":{"canonicalProductId":{"$ref":"#/components/schemas/Uuid"},"confidence":{"type":"number"},"impaCode":{"type":"string"},"name":{"type":"string"},"rank":{"minimum":1,"type":"integer"},"standardUnitCode":{"type":"string"}},"required":["canonicalProductId","name","rank"],"type":"object"},"MatchCanonicalProductsRequest":{"properties":{"items":{"items":{"$ref":"#/components/schemas/MatchItemInput"},"maxItems":100,"minItems":1,"type":"array"},"maxCandidates":{"default":5,"description":"Cap on semantic candidates returned per line. Exact-tier hits are returned regardless (a duplicate-name multi-hit demotes to candidates and must be shown whole).","maximum":20,"minimum":1,"type":"integer"}},"required":["items"],"type":"object"},"MatchCanonicalProductsResult":{"properties":{"degraded":{"description":"True when the semantic tier could not serve this batch (embed channel unavailable or the tenant's daily embed budget exhausted) — the exact and tenant-mapping tiers still did.","type":"boolean"},"items":{"items":{"$ref":"#/components/schemas/MatchItemResult"},"type":"array"}},"required":["items","degraded"],"type":"object"},"MatchItemInput":{"properties":{"ref":{"description":"Caller correlation token, echoed back on the item's result.","maxLength":64,"type":"string"},"text":{"maxLength":500,"minLength":1,"type":"string"}},"required":["text"],"type":"object"},"MatchItemResult":{"properties":{"candidates":{"items":{"$ref":"#/components/schemas/MatchCandidate"},"type":"array"},"matchedVia":{"description":"exact-name / synonym / tenant-mapping are the confident tiers (auto-linkable when exactly one candidate); semantic candidates are suggestions requiring an explicit pick; none = nothing served (the free-text line stays valid).","enum":["exact-name","synonym","tenant-mapping","semantic","none"],"type":"string"},"ref":{"type":"string"},"text":{"description":"The line as matched — NERMO's normalized form (upper, trimmed, whitespace-collapsed).","type":"string"}},"required":["text","matchedVia","candidates"],"type":"object"},"McpConnection":{"description":"One live hosted-connector grant: an AI application a person authorized on the NERMO connect page. applicationName is derived from the surface's OAuth callback host at authorization (Claude, Grok, ChatGPT, …) — every hosted surface shares one public client id, so the id alone cannot name the surface.","properties":{"applicationName":{"description":"The surface name shown to people (Claude, Grok, …; \"AI assistant\" when the callback host is unknown).","type":"string"},"approvedFromIp":{"description":"The approving browser's IP at authorization, when known.","type":"string"},"approvedUserAgent":{"description":"The approving browser's user agent at authorization, when known.","type":"string"},"callbackHost":{"description":"The OAuth callback host the grant returns to — the fact behind applicationName.","type":"string"},"connectedAt":{"$ref":"#/components/schemas/Timestamp"},"id":{"$ref":"#/components/schemas/Uuid"},"oauthClientId":{"type":"string"}},"required":["id","applicationName","callbackHost","oauthClientId","connectedAt"],"type":"object"},"McpConnectionList":{"description":"Unpaginated — a person holds a handful of connections.","properties":{"data":{"items":{"$ref":"#/components/schemas/McpConnection"},"type":"array"}},"required":["data"],"type":"object"},"MenuPlan":{"properties":{"allowances":{"description":"Present on getMenuPlan and every write response — the plan's own daily-allowance list (seeded from the vessel's or the source template's at creation; edited via setMenuPlanAllowances while draft).","items":{"$ref":"#/components/schemas/Allowance"},"type":"array"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"crewComplement":{"nullable":true,"type":"integer"},"durationDays":{"nullable":true,"type":"integer"},"forkedFromMenuPlanId":{"format":"uuid","nullable":true,"type":"string"},"generatedRfqIds":{"description":"Present on getMenuPlan only — the reverse read of rfqs.menu_plan_id (re-generation makes it one-to-many).","items":{"$ref":"#/components/schemas/Uuid"},"type":"array"},"id":{"$ref":"#/components/schemas/Uuid"},"isTemplate":{"type":"boolean"},"name":{"type":"string"},"shared":{"description":"true = shared NERMO corpus template (tenant NULL, read-only to clients — since 000135, a client's own writes/transitions on it 404). Copy it into an own plan via createMenuPlan's fromMenuPlanId instead of editing in place.","type":"boolean"},"slots":{"description":"Present on getMenuPlan only.","items":{"$ref":"#/components/schemas/MenuPlanSlot"},"type":"array"},"startDate":{"format":"date","nullable":true,"type":"string"},"status":{"enum":["draft","confirmed","rfq-generated","archived"],"type":"string"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"},"vesselId":{"format":"uuid","nullable":true,"type":"string"}},"required":["id","name","isTemplate","status","shared","createdAt","updatedAt"],"type":"object"},"MenuPlanList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/MenuPlan"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"MenuPlanSlot":{"properties":{"dayIndex":{"minimum":0,"type":"integer"},"id":{"$ref":"#/components/schemas/Uuid"},"mealSlot":{"enum":["breakfast","mid-morning","lunch","tea","dinner","night"],"type":"string"},"recipeId":{"$ref":"#/components/schemas/Uuid"},"recipeName":{"type":"string"},"recipeServes":{"type":"integer"},"recipeStatus":{"description":"The referenced recipe's CURRENT status (live-read) — the planner surfaces archived-while-slotted.","enum":["draft","published","archived"],"type":"string"},"servingsOverride":{"nullable":true,"type":"integer"}},"required":["id","dayIndex","mealSlot","recipeId"],"type":"object"},"MenuTemplate":{"description":"A shared-corpus menu template — a tenant-less menu plan NERMO staff author; confirmed = the founder's sign-off. Reuses the plan's slot shape.","properties":{"allowances":{"description":"Present on getMenuTemplate and every write response — the template's own daily-allowance list, copied onto a plan created from it.","items":{"$ref":"#/components/schemas/Allowance"},"type":"array"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"crewProfile":{"enum":["filipino","indian","chinese","indonesian","ukrainian-eastern-european","western-northern-european","south-american"],"nullable":true,"type":"string"},"durationDays":{"nullable":true,"type":"integer"},"id":{"$ref":"#/components/schemas/Uuid"},"name":{"type":"string"},"slots":{"description":"Present on getMenuTemplate and every write response.","items":{"$ref":"#/components/schemas/MenuPlanSlot"},"type":"array"},"status":{"enum":["draft","confirmed","archived"],"type":"string"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"}},"required":["id","name","crewProfile","durationDays","status","createdAt","updatedAt"],"type":"object"},"MenuTemplateList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/MenuTemplate"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"Money":{"properties":{"amount":{"$ref":"#/components/schemas/DecimalString"},"currency":{"$ref":"#/components/schemas/CurrencyCode"}},"required":["amount","currency"],"type":"object"},"MoveProductSynonym":{"properties":{"canonicalProductId":{"$ref":"#/components/schemas/Uuid"}},"required":["canonicalProductId"],"type":"object"},"NextTierDelta":{"properties":{"ratePct":{"$ref":"#/components/schemas/DecimalString"},"tier":{"enum":["mid","complete"],"type":"string"},"unmet":{"items":{"type":"string"},"type":"array"}},"required":["tier","ratePct","unmet"],"type":"object"},"NotificationPreference":{"properties":{"defaultEnabled":{"description":"The template's default posture — true = sends by default (opt-out), false = opt-in.","type":"boolean"},"enabled":{"description":"The effective value for the calling user — the explicit row if one exists, else the default.","type":"boolean"},"templateId":{"description":"Catalog email-template id (e.g. client-getting-started-rfq).","type":"string"}},"required":["templateId","defaultEnabled","enabled"],"type":"object"},"NotificationPreferenceList":{"description":"Unpaginated — the roster is a fixed per-party table of a handful of templates.","properties":{"data":{"items":{"$ref":"#/components/schemas/NotificationPreference"},"type":"array"}},"required":["data"],"type":"object"},"ObservationEvidence":{"properties":{"createdAt":{"format":"date-time","type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"sourceText":{"type":"string"},"tenantId":{"$ref":"#/components/schemas/Uuid"},"tenantName":{"type":"string"}},"required":["id","tenantId","tenantName","sourceText","createdAt"],"type":"object"},"ObservationEvidenceList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ObservationEvidence"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"OnboardingCase":{"allOf":[{"$ref":"#/components/schemas/OnboardingCaseSummary"},{"properties":{"activatedAt":{"format":"date-time","nullable":true,"type":"string"},"application":{"description":"The submitted application snapshot (contact PII, never owners/UBO — those live encrypted in kyc_parties and surface as screening entities and, since the Company-profile slice, as the audited {role, name} directors sub-resource; dob_enc/nationality_enc are never projected)."},"awaitingPaymentAt":{"format":"date-time","nullable":true,"type":"string"},"bankingVerifiedAt":{"format":"date-time","nullable":true,"type":"string"},"checklist":{"$ref":"#/components/schemas/VendorOnboardingChecklist"},"contactEmailVerified":{"type":"boolean"},"decisionReason":{"nullable":true,"type":"string"},"kycPendingAt":{"format":"date-time","nullable":true,"type":"string"},"rejectedAt":{"format":"date-time","nullable":true,"type":"string"},"riskScore":{"nullable":true,"type":"number"},"screeningAt":{"format":"date-time","nullable":true,"type":"string"},"screeningResults":{"items":{"$ref":"#/components/schemas/OnboardingScreeningResult"},"type":"array"},"termsAcceptedAt":{"format":"date-time","nullable":true,"type":"string"},"termsVersion":{"nullable":true,"type":"string"}},"required":["application","contactEmailVerified","screeningResults"],"type":"object"}]},"OnboardingCaseList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/OnboardingCaseSummary"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"OnboardingCaseSummary":{"properties":{"caseType":{"$ref":"#/components/schemas/OnboardingCaseType"},"companyName":{"nullable":true,"type":"string"},"contactEmail":{"nullable":true,"type":"string"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"decision":{"description":"approved | declined; null while undecided.","nullable":true,"type":"string"},"duplicateHint":{"description":"Another case shares this contact email or company name (the resubmission signal).","type":"boolean"},"escalated":{"description":"escalated_at set and no decision yet — the queue's action lane.","type":"boolean"},"reference":{"type":"string"},"reviewPending":{"description":"At least one screened entity still awaits a staff disposition (latest result manual-review) — under the review-after posture an active client case can carry this.","type":"boolean"},"status":{"description":"onboarding_status for client cases; vendor_status for vendor cases.","type":"string"},"tenantId":{"$ref":"#/components/schemas/Uuid"}},"required":["caseType","tenantId","reference","status","escalated","createdAt","duplicateHint","reviewPending"],"type":"object"},"OnboardingCaseType":{"enum":["client","vendor"],"type":"string"},"OnboardingScreeningResult":{"properties":{"entityId":{"$ref":"#/components/schemas/Uuid"},"entityName":{"description":"Human name of the screened entity — the applicant's legal name (tenant), the vessel name, or the decrypted kyc-party name. Kyc-party disclosure writes one action='accessed' audit row per party, the directors-read posture.","type":"string"},"entityType":{"enum":["tenant","vessel","kyc-party"],"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"matchDetails":{"description":"Provider hit payload — STAFF-ONLY by op placement (this schema is only served under nermo.onboarding.view)."},"provider":{"type":"string"},"result":{"enum":["clear","hit","manual-review","error"],"type":"string"},"screenedAt":{"$ref":"#/components/schemas/Timestamp"},"screenedByActor":{"format":"uuid","nullable":true,"type":"string"}},"required":["id","entityType","entityId","provider","result","screenedAt"],"type":"object"},"Order":{"properties":{"agreedDueAt":{"format":"date","nullable":true,"type":"string"},"client":{"$ref":"#/components/schemas/ClientRef"},"clientTenantId":{"$ref":"#/components/schemas/Uuid"},"computedStatus":{"description":"Client-facing unified status — read-layer projection over (rfq, order, delivery) status, not a stored enum.","type":"string"},"confirmedAt":{"format":"date-time","nullable":true,"type":"string"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"currency":{"$ref":"#/components/schemas/CurrencyCode"},"declineNote":{"nullable":true,"type":"string"},"declineReason":{"enum":["out-of-stock","cannot-make-eta","flagged-counterparty","other"],"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"lines":{"items":{"$ref":"#/components/schemas/OrderLine"},"type":"array"},"port":{"$ref":"#/components/schemas/PortRef"},"portId":{"$ref":"#/components/schemas/Uuid"},"quoteId":{"$ref":"#/components/schemas/Uuid"},"quoteOrdinal":{"nullable":true,"type":"integer"},"reference":{"type":"string"},"respondBy":{"format":"date-time","nullable":true,"type":"string"},"rfqId":{"$ref":"#/components/schemas/Uuid"},"rfqReference":{"type":"string"},"settlement":{"$ref":"#/components/schemas/SettlementSummary"},"status":{"enum":["awaiting-vendor-acceptance","confirmed","delivered","invoiced","cancelled","declined","retracted"],"type":"string"},"terms":{"$ref":"#/components/schemas/PaymentTerms"},"totalAmount":{"$ref":"#/components/schemas/Money"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"},"vendor":{"$ref":"#/components/schemas/VendorRef"},"vendorTenantId":{"$ref":"#/components/schemas/Uuid"},"vessel":{"$ref":"#/components/schemas/VesselRef"},"vesselId":{"$ref":"#/components/schemas/Uuid"}},"required":["id","reference","clientTenantId","vendorTenantId","rfqId","rfqReference","quoteId","vesselId","portId","status","computedStatus","currency","lines","createdAt","updatedAt"],"type":"object"},"OrderDocument":{"description":"The vendor's own goods invoice or credit note attached to an order (vendor agreement §7.6). NERMO issues nothing on the vendor's behalf: the metadata is the vendor's declaration, the bytes ride the non-contract file route (GET /orders/{orderId}/documents/{docId}/file). A superseded row (supersededBy set) stays listed and downloadable — the vendor re-issued that reference — and a different reference is a different document, so an order may carry several live invoices and several credit notes.","properties":{"clientNotifiedAt":{"format":"date-time","nullable":true,"type":"string"},"creditsDocumentId":{"format":"uuid","nullable":true,"type":"string"},"currency":{"$ref":"#/components/schemas/CurrencyCode"},"docType":{"enum":["invoice","credit-note"],"type":"string"},"documentReference":{"description":"The vendor's own invoice / credit-note number — the client's payment reference.","type":"string"},"dueAt":{"format":"date","nullable":true,"type":"string"},"effectiveDueAt":{"format":"date","nullable":true,"type":"string"},"fileName":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"issuedAt":{"description":"The vendor's document date.","format":"date","type":"string"},"orderId":{"$ref":"#/components/schemas/Uuid"},"orderReference":{"type":"string"},"reconciliation":{"description":"A staff flag against the vendor's declared figures, never a block. invoice: matched only when BOTH the currency equals the order's and the total equals the order's delivered value (the rebate base); a difference on either is mismatch. credit-note: always not-applicable.","enum":["matched","mismatch","not-applicable"],"type":"string"},"sizeBytes":{"minimum":0,"nullable":true,"type":"integer"},"supersededBy":{"format":"uuid","nullable":true,"type":"string"},"totalAmount":{"$ref":"#/components/schemas/Money"},"uploadedAt":{"$ref":"#/components/schemas/Timestamp"},"withdrawnBy":{"format":"uuid","nullable":true,"type":"string"}},"required":["id","orderId","orderReference","docType","documentReference","currency","totalAmount","issuedAt","reconciliation","uploadedAt"],"type":"object"},"OrderLine":{"properties":{"canonicalProductId":{"format":"uuid","nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"qty":{"$ref":"#/components/schemas/DecimalString"},"quoteLineId":{"$ref":"#/components/schemas/Uuid"},"sourceText":{"type":"string"},"unitCode":{"type":"string"},"unitPrice":{"$ref":"#/components/schemas/Money"}},"required":["id","quoteLineId","sourceText","qty","unitCode","unitPrice"],"type":"object"},"OrderList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Order"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"OrderPayment":{"properties":{"createdAt":{"$ref":"#/components/schemas/Timestamp"},"grossAmount":{"$ref":"#/components/schemas/Money"},"id":{"$ref":"#/components/schemas/Uuid"},"method":{"enum":["direct","stripe"],"type":"string"},"orderId":{"$ref":"#/components/schemas/Uuid"},"orderReference":{"type":"string"},"settledAt":{"format":"date-time","nullable":true,"type":"string"},"status":{"enum":["pending","collected","released","settled","failed","reversed"],"type":"string"},"surchargeAmount":{"$ref":"#/components/schemas/Money"},"totalCharged":{"$ref":"#/components/schemas/Money"}},"required":["id","orderId","orderReference","method","grossAmount","surchargeAmount","totalCharged","status","createdAt"],"type":"object"},"OrderPaymentList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/OrderPayment"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"OverrideCaseRequest":{"properties":{"action":{"description":"force-advance = write the approval decision out of band (the O-13 re-read converges); restart = reset the trigger so the drainer re-drives (only starts when the prior run is genuinely failed/closed).","enum":["force-advance","restart"],"type":"string"},"reason":{"minLength":1,"type":"string"}},"required":["action","reason"],"type":"object"},"Pagination":{"properties":{"page":{"minimum":1,"type":"integer"},"pageSize":{"minimum":1,"type":"integer"},"total":{"minimum":0,"type":"integer"},"totalPages":{"minimum":0,"type":"integer"}},"required":["page","pageSize","total","totalPages"],"type":"object"},"PaymentTerms":{"description":"Vendor→client goods payment terms. prepaid = payment before delivery (NOT net-0); net = due N days after first delivery (design 26-08-03 R-11/R-14).","properties":{"kind":{"enum":["prepaid","net"],"type":"string"},"netDays":{"description":"Required when kind=net; absent for prepaid.","maximum":365,"minimum":1,"nullable":true,"type":"integer"}},"required":["kind"],"type":"object"},"Permission":{"properties":{"action":{"type":"string"},"capability":{"type":"string"},"description":{"type":"string"},"key":{"description":"Canonical permission key, format module.capability.action (catalog § permission_format).","type":"string"},"module":{"type":"string"},"party":{"description":"Tenant-CLASS axis (catalog § permission_keys party). A tenant may mint a custom role only from keys of its own party plus the party-neutral `shared` set; consumers filter the delegatable set on this.","enum":["client","vendor","shared","staff"],"type":"string"}},"required":["key","module","capability","action","party"],"type":"object"},"PermissionList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Permission"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"Port":{"properties":{"countryCode":{"maxLength":2,"minLength":2,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"name":{"type":"string"},"status":{"enum":["active","inactive"],"type":"string"},"timezone":{"type":"string"},"unlocode":{"maxLength":5,"minLength":5,"type":"string"}},"required":["id","unlocode","name","countryCode","timezone","status"],"type":"object"},"PortList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Port"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"PortRef":{"description":"Nested read reference to a port (display denormalization — ports are tenant-agnostic reference data, so the ref is populated on every read; there is no port-by-id endpoint).","properties":{"name":{"type":"string"},"unlocode":{"maxLength":5,"minLength":5,"type":"string"}},"required":["name","unlocode"],"type":"object"},"PreferredVendor":{"properties":{"createdAt":{"$ref":"#/components/schemas/Timestamp"},"port":{"$ref":"#/components/schemas/PortRef"},"portId":{"$ref":"#/components/schemas/Uuid"},"vendor":{"$ref":"#/components/schemas/VendorRef"},"vendorTenantId":{"$ref":"#/components/schemas/Uuid"}},"required":["portId","vendorTenantId","createdAt"],"type":"object"},"PreferredVendorList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/PreferredVendor"},"type":"array"}},"required":["data"],"type":"object"},"PriceMapBasket":{"properties":{"costableLines":{"type":"integer"},"lines":{"type":"integer"},"menuPlanTemplateId":{"$ref":"#/components/schemas/Uuid"},"name":{"type":"string"}},"required":["menuPlanTemplateId","name","lines","costableLines"],"type":"object"},"PriceMapPort":{"properties":{"cheapestLocalCurrency":{"nullable":true,"type":"string"},"cheapestLocalPrice":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"},"cheapestPriceAgeDays":{"nullable":true,"type":"integer"},"cheapestStale":{"type":"boolean"},"cheapestUsd":{"$ref":"#/components/schemas/DecimalString"},"cheapestVendorName":{"nullable":true,"type":"string"},"countryCode":{"maxLength":2,"minLength":2,"type":"string"},"currencies":{"items":{"type":"string"},"type":"array"},"medianUsd":{"$ref":"#/components/schemas/DecimalString"},"name":{"type":"string"},"oldestContributingPriceAgeDays":{"nullable":true,"type":"integer"},"region":{"nullable":true,"type":"string"},"unlocode":{"maxLength":5,"minLength":5,"type":"string"},"unpricedCostShare":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"},"unpricedLines":{"nullable":true,"type":"integer"},"vendorsCount":{"minimum":1,"type":"integer"}},"required":["unlocode","name","countryCode","region","vendorsCount","cheapestUsd","medianUsd","cheapestPriceAgeDays","oldestContributingPriceAgeDays","cheapestStale","currencies"],"type":"object"},"PriceMapProduct":{"properties":{"id":{"$ref":"#/components/schemas/Uuid"},"name":{"type":"string"},"unitCode":{"nullable":true,"type":"string"}},"required":["id","name","unitCode"],"type":"object"},"ProcurementJourney":{"description":"One procurement journey — an RFQ and everything it becomes. A read-layer projection over (rfq | dispatch, order, latest delivery); journeyStage unifies the pre-award root status and the post-award computed status into one lifecycle, derived server-side in SQL. The client stage set is draft|sent|quoted|ordered|in-transit|delivered|exception|invoiced|closed; the vendor set is new|viewed|quoted|won|in-transit|delivered|exception|invoiced|lapsed.","properties":{"currency":{"$ref":"#/components/schemas/CurrencyCode"},"dispatchId":{"format":"uuid","nullable":true,"type":"string"},"hasException":{"type":"boolean"},"headlineAmount":{"nullable":true,"type":"string"},"journeyStage":{"type":"string"},"orderId":{"format":"uuid","nullable":true,"type":"string"},"orderReference":{"nullable":true,"type":"string"},"port":{"$ref":"#/components/schemas/PortRef"},"portId":{"$ref":"#/components/schemas/Uuid"},"rfqId":{"$ref":"#/components/schemas/Uuid"},"rfqReference":{"type":"string"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"},"vessel":{"properties":{"imo":{"nullable":true,"type":"string"},"name":{"type":"string"}},"required":["name"],"type":"object"},"vesselId":{"format":"uuid","nullable":true,"type":"string"}},"required":["rfqId","rfqReference","portId","journeyStage","hasException","updatedAt"],"type":"object"},"ProcurementJourneyList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ProcurementJourney"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"ProductCategory":{"properties":{"code":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"name":{"type":"string"},"parentId":{"format":"uuid","nullable":true,"type":"string"}},"required":["id","code","name"],"type":"object"},"ProductCategoryList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ProductCategory"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"ProductPriceMap":{"properties":{"basket":{"$ref":"#/components/schemas/PriceMapBasket"},"coveredPorts":{"type":"integer"},"globalMedianCheapestUsd":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"},"globalMedianMedianUsd":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"},"mode":{"enum":["product","basket"],"type":"string"},"ports":{"items":{"$ref":"#/components/schemas/PriceMapPort"},"type":"array"},"product":{"$ref":"#/components/schemas/PriceMapProduct"},"rateDate":{"format":"date","nullable":true,"type":"string"},"unconvertibleOffers":{"type":"integer"},"universePorts":{"type":"integer"}},"required":["mode","rateDate","ports","globalMedianCheapestUsd","globalMedianMedianUsd","coveredPorts","universePorts","unconvertibleOffers"],"type":"object"},"ProductSynonym":{"properties":{"canonicalProductId":{"$ref":"#/components/schemas/Uuid"},"createdAt":{"format":"date-time","type":"string"},"createdFromRef":{"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"reviewedByActor":{"$ref":"#/components/schemas/Uuid"},"synonymNorm":{"type":"string"}},"required":["id","synonymNorm","reviewedByActor","createdAt"],"type":"object"},"ProductSynonymList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ProductSynonym"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"Prospect":{"properties":{"companyName":{"type":"string"},"contactEmail":{"nullable":true,"type":"string"},"contactName":{"nullable":true,"type":"string"},"convertedClientId":{"format":"uuid","nullable":true,"type":"string"},"country":{"nullable":true,"type":"string"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"id":{"$ref":"#/components/schemas/Uuid"},"notes":{"nullable":true,"type":"string"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"}},"required":["id","companyName","contactName","contactEmail","country","notes","convertedClientId","createdAt","updatedAt"],"type":"object"},"ProspectInput":{"properties":{"companyName":{"maxLength":200,"minLength":1,"type":"string"},"contactEmail":{"maxLength":320,"type":"string"},"contactName":{"maxLength":200,"type":"string"},"country":{"maxLength":2,"minLength":2,"type":"string"},"notes":{"maxLength":4000,"type":"string"}},"required":["companyName"],"type":"object"},"ProspectList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Prospect"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"ProspectSimulation":{"properties":{"computedAt":{"$ref":"#/components/schemas/Timestamp"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"figures":{"$ref":"#/components/schemas/MandayEstimate"},"id":{"$ref":"#/components/schemas/Uuid"},"inputs":{"additionalProperties":true,"type":"object"},"name":{"type":"string"},"prospectId":{"$ref":"#/components/schemas/Uuid"}},"required":["id","prospectId","name","inputs","figures","computedAt","createdAt"],"type":"object"},"ProspectSimulationInput":{"properties":{"computedAt":{"$ref":"#/components/schemas/Timestamp"},"figures":{"$ref":"#/components/schemas/MandayEstimate"},"inputs":{"additionalProperties":true,"type":"object"},"name":{"maxLength":200,"minLength":1,"type":"string"}},"required":["name","inputs","figures"],"type":"object"},"ProspectSimulationList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ProspectSimulationSummary"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"ProspectSimulationSummary":{"properties":{"blendedCheapestUsd":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"},"computedAt":{"$ref":"#/components/schemas/Timestamp"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"crewTotal":{"type":"integer"},"id":{"$ref":"#/components/schemas/Uuid"},"name":{"type":"string"},"portsCount":{"type":"integer"},"prospectId":{"$ref":"#/components/schemas/Uuid"}},"required":["id","prospectId","name","computedAt","createdAt","blendedCheapestUsd","crewTotal","portsCount"],"type":"object"},"ProspectUpdate":{"description":"Full replace.","properties":{"companyName":{"maxLength":200,"minLength":1,"type":"string"},"contactEmail":{"maxLength":320,"nullable":true,"type":"string"},"contactName":{"maxLength":200,"nullable":true,"type":"string"},"convertedClientId":{"format":"uuid","nullable":true,"type":"string"},"country":{"maxLength":2,"minLength":2,"nullable":true,"type":"string"},"notes":{"maxLength":4000,"nullable":true,"type":"string"}},"required":["companyName"],"type":"object"},"PublicCertification":{"properties":{"docType":{"type":"string"},"status":{"enum":["verified","expiring","expired"],"type":"string"}},"required":["docType","status"],"type":"object"},"PublicPortRef":{"properties":{"name":{"type":"string"},"unlocode":{"maxLength":5,"minLength":5,"type":"string"}},"required":["name","unlocode"],"type":"object"},"PublicVendorList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/PublicVendorSummary"},"type":"array"}},"required":["data"],"type":"object"},"PublicVendorPage":{"properties":{"categories":{"items":{"$ref":"#/components/schemas/CostCategory"},"type":"array"},"certifications":{"items":{"$ref":"#/components/schemas/PublicCertification"},"type":"array"},"name":{"type":"string"},"primaryPort":{"$ref":"#/components/schemas/PublicPortRef"},"slug":{"type":"string"}},"required":["slug","name","categories","certifications"],"type":"object"},"PublicVendorSummary":{"properties":{"categories":{"items":{"$ref":"#/components/schemas/CostCategory"},"type":"array"},"name":{"type":"string"},"primaryPort":{"$ref":"#/components/schemas/PublicPortRef"},"slug":{"type":"string"}},"required":["slug","name","categories"],"type":"object"},"PublishCorpusRecipe":{"properties":{"attestedAllergenReview":{"description":"Must be true — staff attests the allergen/dietary data has been verified against source (G-4). Recorded in the publish audit row.","type":"boolean"}},"required":["attestedAllergenReview"],"type":"object"},"Quote":{"properties":{"createdAt":{"$ref":"#/components/schemas/Timestamp"},"currency":{"$ref":"#/components/schemas/CurrencyCode"},"id":{"$ref":"#/components/schemas/Uuid"},"lines":{"items":{"$ref":"#/components/schemas/QuoteLine"},"type":"array"},"notes":{"nullable":true,"type":"string"},"ordinal":{"description":"The quote's 1-based position among the RFQ's submitted quotes, by submission time. Null while draft (unsubmitted quotes are outside the numbering) and null on vendor-party reads (the ordinal would disclose how many competitors quoted first).","nullable":true,"type":"integer"},"rfqId":{"$ref":"#/components/schemas/Uuid"},"rfqReference":{"type":"string"},"rfqRoutingId":{"$ref":"#/components/schemas/Uuid"},"status":{"enum":["draft","received","accepted","rejected","expired","declined"],"type":"string"},"submittedAt":{"format":"date-time","nullable":true,"type":"string"},"terms":{"$ref":"#/components/schemas/PaymentTerms"},"totalAmount":{"$ref":"#/components/schemas/Money"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"},"validUntil":{"format":"date-time","nullable":true,"type":"string"},"vendor":{"$ref":"#/components/schemas/VendorRef"},"vendorTenantId":{"$ref":"#/components/schemas/Uuid"}},"required":["id","vendorTenantId","rfqId","rfqReference","rfqRoutingId","status","currency","lines","createdAt","updatedAt"],"type":"object"},"QuoteLine":{"properties":{"canonicalProductId":{"format":"uuid","nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"leadTimeDays":{"nullable":true,"type":"integer"},"notes":{"nullable":true,"type":"string"},"qty":{"$ref":"#/components/schemas/DecimalString"},"rfqLineId":{"$ref":"#/components/schemas/Uuid"},"sourceCatalogItemId":{"format":"uuid","nullable":true,"type":"string"},"sourceText":{"type":"string"},"substituteOfferedId":{"format":"uuid","nullable":true,"type":"string"},"substituteOfferedName":{"nullable":true,"type":"string"},"unitCode":{"type":"string"},"unitPrice":{"$ref":"#/components/schemas/Money"}},"required":["id","rfqLineId","sourceText","qty","unitCode","unitPrice"],"type":"object"},"QuoteSummary":{"properties":{"currency":{"$ref":"#/components/schemas/CurrencyCode"},"id":{"$ref":"#/components/schemas/Uuid"},"ordinal":{"description":"The quote's 1-based position among the RFQ's submitted quotes, by submission time — the positional label \"Quote 2 · NER-RFQ-1041\" (quotes carry no number of their own). Null on vendor-party reads: the ordinal would disclose how many competitors quoted first.","nullable":true,"type":"integer"},"status":{"enum":["received","accepted","rejected","expired","declined"],"type":"string"},"terms":{"$ref":"#/components/schemas/PaymentTerms"},"totalAmount":{"$ref":"#/components/schemas/Money"},"validUntil":{"format":"date-time","nullable":true,"type":"string"},"vendor":{"$ref":"#/components/schemas/VendorRef"},"vendorTenantId":{"$ref":"#/components/schemas/Uuid"}},"required":["id","vendorTenantId","status","currency"],"type":"object"},"ReactivateVessel":{"description":"Reactivation is a fresh fee acceptance (the vessel re-enters pending and bills from its activation).","properties":{"subscriptionAcceptance":{"$ref":"#/components/schemas/SubscriptionAcceptance"}},"required":["subscriptionAcceptance"],"type":"object"},"RebateCurrencySummary":{"description":"Whole-tenant running totals in the settlement currency (one USD row).","properties":{"accrued":{"$ref":"#/components/schemas/DecimalString"},"currency":{"$ref":"#/components/schemas/CurrencyCode"},"invoiced":{"$ref":"#/components/schemas/DecimalString"},"settled":{"$ref":"#/components/schemas/DecimalString"}},"required":["currency","accrued","invoiced","settled"],"type":"object"},"RebateRateInfo":{"description":"The vendor's rebate rate standing today, plus the ladder panel data (design 26-08-03 R-6). negotiated=true → tier is null, criteria/nextTier are omitted (the rate is individually agreed; the ladder does not apply).","properties":{"criteria":{"items":{"properties":{"detail":{"description":"e.g. \"18/25 active items\", \"provisions 3/10 linked items\"","nullable":true,"type":"string"},"graceExpiresAt":{"description":"set while this criterion is lapsed inside its 30-day window.","format":"date","nullable":true,"type":"string"},"key":{"enum":["M1","M2","M3","M4","C1","C2","C3"],"type":"string"},"met":{"type":"boolean"},"vendorActionable":{"description":"false = the \"contact NERMO\" boxes (staff-filled identity fields).","type":"boolean"}},"required":["key","met","vendorActionable"],"type":"object"},"type":"array"},"effectiveFrom":{"format":"date","type":"string"},"negotiated":{"type":"boolean"},"nextTier":{"$ref":"#/components/schemas/NextTierDelta"},"priceListConfirmedAt":{"format":"date-time","nullable":true,"type":"string"},"ratePct":{"$ref":"#/components/schemas/DecimalString"},"tier":{"enum":["base","mid","complete"],"nullable":true,"type":"string"}},"required":["ratePct","effectiveFrom","negotiated"],"type":"object"},"RecategorizeCanonicalProduct":{"properties":{"categoryId":{"$ref":"#/components/schemas/Uuid"}},"required":["categoryId"],"type":"object"},"Receivable":{"properties":{"agingBucket":{"enum":["current","1-30","31-60","61-90","90-plus"],"type":"string"},"client":{"$ref":"#/components/schemas/ClientRef"},"currency":{"$ref":"#/components/schemas/CurrencyCode"},"deliveredAt":{"$ref":"#/components/schemas/Timestamp"},"dueAt":{"description":"The goods due date (design R-14), at UTC midnight. net = FIRST delivery + netDays, or, where any live goods invoice states its own due date, the EARLIEST such date across the live invoices (an order may carry several live invoices for part deliveries and is past due as soon as its first one is; the agreed date is a floor at upload, so a stated date can extend it, never shorten it); prepaid = the first delivery's conclusion date (the client's confirm or the auto-conclude) — on the direct route payment preceded delivery so a delivered unpaid prepaid order is past due, and on the pre-funded route that date is when NERMO releases the held funds; the invoice's stated date never moves a prepaid anchor; null only for terms-less legacy orders.","format":"date-time","nullable":true,"type":"string"},"orderId":{"$ref":"#/components/schemas/Uuid"},"orderReference":{"type":"string"},"outstanding":{"$ref":"#/components/schemas/Money"},"overdue":{"description":"True once the due DATE has passed: from the first UTC day after dueAt, so on the due date itself the receivable is still current (agingBucket agrees — the two derive from the same day count). False when dueAt is null — legacy rows age off deliveredAt only and are never overdue.","type":"boolean"},"recordedTotal":{"$ref":"#/components/schemas/Money"},"totalAmount":{"$ref":"#/components/schemas/Money"},"vesselId":{"format":"uuid","nullable":true,"type":"string"}},"required":["orderId","orderReference","currency","totalAmount","recordedTotal","outstanding","deliveredAt","agingBucket"],"type":"object"},"ReceivableList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Receivable"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"Recipe":{"properties":{"allergens":{"items":{"enum":["celery","crustaceans","eggs","fish","gluten","lupin","milk","molluscs","mustard","nuts","peanuts","sesame","soybeans","sulphites"],"type":"string"},"type":"array"},"category":{"nullable":true,"type":"string"},"cookMin":{"nullable":true,"type":"integer"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"cuisine":{"nullable":true,"type":"string"},"dietaryTags":{"items":{"enum":["halal","kosher","vegetarian","vegan","pescatarian","no-pork","no-beef"],"type":"string"},"type":"array"},"forkedFromRecipeId":{"format":"uuid","nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"ingredients":{"description":"Present on getRecipe only.","items":{"$ref":"#/components/schemas/RecipeIngredient"},"type":"array"},"method":{"nullable":true,"type":"string"},"name":{"type":"string"},"prepMin":{"nullable":true,"type":"integer"},"serves":{"description":"Base yield — the generator scales by servings / serves.","minimum":1,"type":"integer"},"shared":{"description":"true = shared NERMO corpus row (tenant NULL, read-only — edit by forking).","type":"boolean"},"status":{"enum":["draft","published","archived"],"type":"string"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"}},"required":["id","name","serves","status","shared","allergens","dietaryTags","createdAt","updatedAt"],"type":"object"},"RecipeIngredient":{"properties":{"canonicalProductId":{"format":"uuid","nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"ingredientText":{"nullable":true,"type":"string"},"qty":{"$ref":"#/components/schemas/DecimalString"},"sourceText":{"type":"string"},"substituteCanonicalProductId":{"format":"uuid","nullable":true,"type":"string"},"substituteProductActive":{"nullable":true,"type":"boolean"},"substituteProductName":{"nullable":true,"type":"string"},"substitutionPolicy":{"enum":["product-default","no-substitute","recipe-alternate"],"type":"string"},"unitCode":{"type":"string"}},"required":["id","sourceText","qty","unitCode","substitutionPolicy"],"type":"object"},"RecipeList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Recipe"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"RecordInvoicePayment":{"properties":{"amount":{"$ref":"#/components/schemas/DecimalString"},"method":{"description":"e.g. bank-transfer. Currency is server-set from the invoice — never caller-suppliable.","minLength":1,"type":"string"},"receivedAt":{"format":"date-time","type":"string"}},"required":["amount","method"],"type":"object"},"RecordOrderPayment":{"description":"v1 direct-rail mark-paid. Currency is forced to the order currency; surcharge is 0; totalCharged is server-computed. Omitted amount defaults to the order total.","properties":{"amount":{"$ref":"#/components/schemas/DecimalString"},"paidAt":{"description":"The stated payment date; defaults to today.","format":"date","type":"string"}},"type":"object"},"RejectComplianceDocumentRequest":{"properties":{"notes":{"minLength":1,"type":"string"}},"required":["notes"],"type":"object"},"RenameCanonicalProduct":{"properties":{"name":{"maxLength":200,"minLength":1,"type":"string"}},"required":["name"],"type":"object"},"RenameCanonicalProductResult":{"properties":{"previousName":{"type":"string"},"product":{"$ref":"#/components/schemas/CanonicalProduct"}},"required":["product","previousName"],"type":"object"},"ReopenClientContract":{"properties":{"reason":{"minLength":1,"type":"string"}},"required":["reason"],"type":"object"},"ReportDeliveryDelivered":{"properties":{"note":{"maxLength":2000,"nullable":true,"type":"string"},"podDocumentRef":{"description":"Ref to the client-signed delivery note (Spaces URL + checksum convention). Required — the report that starts a client-binding clock must carry the signed evidence.","maxLength":512,"minLength":1,"type":"string"}},"required":["podDocumentRef"],"type":"object"},"ReputationConfig":{"properties":{"counterpartyFlagThreshold":{"type":"integer"},"updatedAt":{"format":"date-time","type":"string"},"vendorSignalThreshold":{"type":"integer"},"windowDays":{"type":"integer"}},"required":["windowDays","counterpartyFlagThreshold","vendorSignalThreshold","updatedAt"],"type":"object"},"ReputationConfigUpdate":{"properties":{"counterpartyFlagThreshold":{"minimum":1,"type":"integer"},"vendorSignalThreshold":{"minimum":1,"type":"integer"},"windowDays":{"minimum":1,"type":"integer"}},"type":"object"},"ReputationCounterpartyRow":{"properties":{"activeHardBlocks":{"type":"integer"},"activeWarns":{"type":"integer"},"barred":{"type":"boolean"},"clientName":{"type":"string"},"clientTenantId":{"$ref":"#/components/schemas/Uuid"},"flaggingVendors":{"type":"integer"},"hulls":{"items":{"properties":{"activeHardBlocks":{"type":"integer"},"activeWarns":{"type":"integer"},"incidentCount":{"type":"integer"},"vesselImo":{"nullable":true,"type":"string"},"vesselName":{"type":"string"}},"required":["vesselName","activeHardBlocks","activeWarns","incidentCount"],"type":"object"},"type":"array"},"incidentCount":{"type":"integer"},"overThreshold":{"type":"boolean"}},"required":["clientTenantId","clientName","flaggingVendors","activeHardBlocks","activeWarns","incidentCount","barred","overThreshold"],"type":"object"},"ReputationVendorRow":{"properties":{"exceptionLineCount":{"type":"integer"},"feedbackCount":{"type":"integer"},"overThreshold":{"type":"boolean"},"vendorName":{"type":"string"},"vendorStatus":{"type":"string"},"vendorTenantId":{"$ref":"#/components/schemas/Uuid"}},"required":["vendorTenantId","vendorName","vendorStatus","feedbackCount","exceptionLineCount","overThreshold"],"type":"object"},"ResolveEscalationRequest":{"properties":{"decision":{"enum":["approve","decline"],"type":"string"},"reason":{"minLength":1,"type":"string"}},"required":["decision","reason"],"type":"object"},"ResolveImportCandidate":{"properties":{"action":{"enum":["promote-synonym","create-canonical","map-to-existing","discard"],"type":"string"},"canonicalProductId":{"$ref":"#/components/schemas/Uuid"},"newCanonical":{"$ref":"#/components/schemas/CreateCanonicalProduct"}},"required":["action"],"type":"object"},"RetagCanonicalProduct":{"properties":{"allergens":{"items":{"enum":["celery","crustaceans","eggs","fish","gluten","lupin","milk","molluscs","mustard","nuts","peanuts","sesame","soybeans","sulphites"],"type":"string"},"type":"array"},"dietaryTags":{"items":{"enum":["halal","kosher","vegetarian","vegan","pescatarian","no-pork","no-beef"],"type":"string"},"type":"array"}},"required":["allergens","dietaryTags"],"type":"object"},"ReverseInvoicePayment":{"properties":{"reason":{"minLength":1,"type":"string"}},"required":["reason"],"type":"object"},"ReviewMappingProposal":{"properties":{"action":{"enum":["approve","reject","retarget"],"type":"string"},"canonicalProductId":{"$ref":"#/components/schemas/Uuid"},"reason":{"maxLength":500,"type":"string"}},"required":["action"],"type":"object"},"Rfq":{"properties":{"createdAt":{"$ref":"#/components/schemas/Timestamp"},"deliveryLocation":{"enum":["berth","anchorage","unknown"],"type":"string"},"eta":{"format":"date-time","nullable":true,"type":"string"},"generation":{"$ref":"#/components/schemas/RfqGeneration"},"id":{"$ref":"#/components/schemas/Uuid"},"lines":{"items":{"$ref":"#/components/schemas/RfqLine"},"type":"array"},"menuPlanId":{"format":"uuid","nullable":true,"type":"string"},"notes":{"nullable":true,"type":"string"},"port":{"$ref":"#/components/schemas/PortRef"},"portId":{"$ref":"#/components/schemas/Uuid"},"quoteSummaries":{"description":"Summaries of quotes received (full detail via GET /quotes/{id}).","items":{"$ref":"#/components/schemas/QuoteSummary"},"type":"array"},"reference":{"type":"string"},"source":{"enum":["portal","mcp","menu-plan"],"type":"string"},"status":{"enum":["draft","sent","replies-received","awarded","closed","cancelled"],"type":"string"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"},"vesselId":{"$ref":"#/components/schemas/Uuid"}},"required":["id","reference","vesselId","portId","source","status","deliveryLocation","lines"],"type":"object"},"RfqDispatch":{"properties":{"counterpartyFlagged":{"description":"Write-once warn stamp at materialization — true when this vendor held any active counterparty flag matching the RFQ's hull (or company for non-payment). A boolean only; never carries which flag or the client identity.","type":"boolean"},"declineNote":{"nullable":true,"type":"string"},"declineReason":{"enum":["out-of-stock","cannot-make-eta","outside-coverage","flagged-counterparty","other"],"type":"string"},"deliveryLocation":{"enum":["berth","anchorage","unknown"],"type":"string"},"dispatchedAt":{"$ref":"#/components/schemas/Timestamp"},"eta":{"format":"date-time","nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"lines":{"items":{"$ref":"#/components/schemas/RfqDispatchLine"},"type":"array"},"notes":{"nullable":true,"type":"string"},"port":{"$ref":"#/components/schemas/PortRef"},"portId":{"$ref":"#/components/schemas/Uuid"},"quoteCurrency":{"allOf":[{"$ref":"#/components/schemas/VendorCurrency"}],"description":"The currency this vendor quotes in — the vendor's own default currency, joined at read time (nothing on the dispatch stores it; the RFQ carries no currency). The quote workbench and a quoting agent default the quote currency to it: it is readable under rfqs.view, which every quoting role holds, where the company profile is not. Absent when the vendor has no company record yet — the dispatch still lists and opens, and the quote must then name its own currency (a draft or submit without one is refused)."},"quoteId":{"format":"uuid","nullable":true,"type":"string"},"respondedAt":{"format":"date-time","nullable":true,"type":"string"},"rfqId":{"$ref":"#/components/schemas/Uuid"},"rfqReference":{"type":"string"},"status":{"enum":["sent","viewed","responded","declined","expired","cancelled"],"type":"string"},"vessel":{"properties":{"imo":{"nullable":true,"type":"string"},"name":{"type":"string"}},"required":["name"],"type":"object"},"viewedAt":{"format":"date-time","nullable":true,"type":"string"}},"required":["id","rfqId","rfqReference","status","vessel","portId","deliveryLocation","dispatchedAt","counterpartyFlagged","lines"],"type":"object"},"RfqDispatchLine":{"properties":{"canonicalProductId":{"format":"uuid","nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"notes":{"nullable":true,"type":"string"},"qty":{"$ref":"#/components/schemas/DecimalString"},"rfqLineId":{"$ref":"#/components/schemas/Uuid"},"sourceText":{"type":"string"},"unitCode":{"type":"string"}},"required":["id","rfqLineId","sourceText","qty","unitCode"],"type":"object"},"RfqDispatchList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/RfqDispatch"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"RfqGeneration":{"description":"The generator's arithmetic for a menu-plan RFQ — present on getRfq once a generation record exists. reducedCount = ordered lines whose quantity stock reduced; coveredCount = lines stock covered entirely (in the Covered-by-stock panel, not on the RFQ).","properties":{"coveredCount":{"minimum":0,"type":"integer"},"lines":{"items":{"$ref":"#/components/schemas/RfqGenerationLine"},"type":"array"},"netting":{"enum":["on","off"],"type":"string"},"oldestCountedAt":{"format":"date","nullable":true,"type":"string"},"reducedCount":{"minimum":0,"type":"integer"}},"required":["netting","oldestCountedAt","reducedCount","coveredCount","lines"],"type":"object"},"RfqGenerationLine":{"description":"One aggregated requirement line of a menu-plan generation — ordered (rfqLineId set) or covered by stock (rfqLineId null; not an RFQ line). requiredQty is the buffered gross rounded up; toOrderQty = max(required − onBoard − substitutes, 0).","properties":{"canonicalProductId":{"format":"uuid","nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"netting":{"enum":["applied","covered","no-stock","unit-mismatch","no-product","off"],"type":"string"},"onBoardCountedAt":{"format":"date","nullable":true,"type":"string"},"onBoardQty":{"$ref":"#/components/schemas/DecimalString"},"requiredQty":{"$ref":"#/components/schemas/DecimalString"},"rfqLineId":{"format":"uuid","nullable":true,"type":"string"},"sourceText":{"type":"string"},"substitutes":{"items":{"$ref":"#/components/schemas/RfqGenerationSubstitute"},"type":"array"},"toOrderQty":{"$ref":"#/components/schemas/DecimalString"},"unitCode":{"type":"string"}},"required":["id","rfqLineId","canonicalProductId","sourceText","unitCode","requiredQty","onBoardQty","onBoardCountedAt","substitutes","toOrderQty","netting"],"type":"object"},"RfqGenerationSubstitute":{"description":"What pass 2 of netting drew from one substitute's stock for this line.","properties":{"canonicalProductId":{"$ref":"#/components/schemas/Uuid"},"countedAt":{"format":"date","nullable":true,"type":"string"},"productName":{"type":"string"},"qty":{"$ref":"#/components/schemas/DecimalString"},"unitCode":{"type":"string"}},"required":["canonicalProductId","productName","qty","unitCode","countedAt"],"type":"object"},"RfqLine":{"properties":{"canonicalProductId":{"format":"uuid","nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"notes":{"nullable":true,"type":"string"},"qty":{"$ref":"#/components/schemas/DecimalString"},"sourceText":{"type":"string"},"unitCode":{"type":"string"}},"required":["id","sourceText","qty","unitCode"],"type":"object"},"RfqList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Rfq"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"Role":{"properties":{"code":{"type":"string"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"id":{"$ref":"#/components/schemas/Uuid"},"isSystem":{"description":"True for NERMO-seeded system-default roles.","type":"boolean"},"name":{"type":"string"},"permissionKeys":{"items":{"type":"string"},"type":"array"},"tenantId":{"$ref":"#/components/schemas/Uuid"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"}},"required":["id","tenantId","code","name","permissionKeys","isSystem","createdAt","updatedAt"],"type":"object"},"RoleList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Role"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"SaveQuoteDraft":{"properties":{"currency":{"$ref":"#/components/schemas/VendorCurrency"},"lines":{"items":{"$ref":"#/components/schemas/SaveQuoteDraftLine"},"maxItems":500,"type":"array"},"notes":{"type":"string"},"terms":{"$ref":"#/components/schemas/PaymentTerms"},"validUntil":{"format":"date-time","type":"string"}},"required":["lines"],"type":"object"},"SaveQuoteDraftLine":{"properties":{"canonicalProductId":{"format":"uuid","nullable":true,"type":"string"},"edited":{"type":"boolean"},"leadTimeDays":{"type":"integer"},"notes":{"type":"string"},"qty":{"$ref":"#/components/schemas/DecimalString"},"rfqLineId":{"$ref":"#/components/schemas/Uuid"},"sourceText":{"type":"string"},"substituteOfferedId":{"format":"uuid","nullable":true,"type":"string"},"unitCode":{"type":"string"},"unitPrice":{"$ref":"#/components/schemas/Money"}},"required":["rfqLineId","qty","unitCode","unitPrice"],"type":"object"},"ScreenTenantRequest":{"description":"Screen a tenant now. Without disposition, the configured provider seam runs and its outcome is recorded (the pending provider records manual-review). With disposition (reason then required), the staff adjudication is recorded as an authoritative sanctions_screening_events row (provider 'manual'); recency wins over provider results — the onboarding-disposition semantics, available without an onboarding case.","properties":{"clientReason":{"allOf":[{"$ref":"#/components/schemas/VesselDeclinedReason"}],"description":"Required with disposition hit on a vessel (400 without it): the client-visible label on the declined vessel. A vessel disposition also flips the vessel — clear: pending → active (stub issued); hit: pending or active → declined (an unpaid stub voided, a paid one stands)."},"disposition":{"enum":["clear","hit"],"type":"string"},"entityId":{"$ref":"#/components/schemas/Uuid"},"entityType":{"default":"tenant","description":"The screened entity. tenant (default) screens or dispositions the company; vessel and kyc-party require entityId (must belong to tenantId, else 404). The provider arm (no disposition) accepts tenant and vessel — the vessel's name and IMO are the screen target; kyc-party is disposition-only (400 without one — its name is encrypted and screened by the intake activity).","enum":["tenant","vessel","kyc-party"],"type":"string"},"reason":{"description":"Required with disposition; recorded in the staff-only match_details lane.","minLength":1,"type":"string"},"tenantId":{"$ref":"#/components/schemas/Uuid"}},"required":["tenantId"],"type":"object"},"ScreeningDispositionRequest":{"description":"Staff adjudication of a screen — recorded as an authoritative sanctions_screening_events row (provider 'manual'); recency wins over the provider result. On a vessel the disposition also flips the vessel: clear moves a pending vessel to active (its subscription stub is issued); hit moves a pending OR active vessel to declined (an unpaid stub is voided, a paid one stands) and requires clientReason — the client-visible label, a fixed vocabulary so a sanctions-derived decline never tips the client off (400 without it).","properties":{"clientReason":{"$ref":"#/components/schemas/VesselDeclinedReason"},"entityId":{"$ref":"#/components/schemas/Uuid"},"entityType":{"enum":["tenant","vessel","kyc-party"],"type":"string"},"reason":{"description":"Staff-only; recorded in the match_details lane.","minLength":1,"type":"string"},"result":{"enum":["clear","hit"],"type":"string"}},"required":["entityType","entityId","result","reason"],"type":"object"},"ScreeningReviewList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/ScreeningReviewSummary"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"ScreeningReviewSummary":{"description":"One review-pending tenant: activated, with at least one screened entity whose latest authoritative result is manual-review. Derived from the screening rows, never stored — a staff clear/hit disposition per entity removes it. Keyed on the tenant, not the case.","properties":{"caseReference":{"description":"The onboarding case reference; null for a staff-provisioned tenant (no case).","nullable":true,"type":"string"},"oldestPendingAt":{"$ref":"#/components/schemas/Timestamp"},"party":{"enum":["client","vendor"],"type":"string"},"pendingEntities":{"description":"Screened entities (company, related parties, vessels) whose latest result is manual-review.","type":"integer"},"tenantId":{"$ref":"#/components/schemas/Uuid"},"tenantName":{"type":"string"}},"required":["tenantId","party","tenantName","caseReference","pendingEntities","oldestPendingAt"],"type":"object"},"ScreeningRunResult":{"properties":{"provider":{"type":"string"},"result":{"enum":["clear","hit","manual-review","error"],"type":"string"},"screenedAt":{"$ref":"#/components/schemas/Timestamp"}},"required":["result","provider","screenedAt"],"type":"object"},"SendRfq":{"properties":{"vendorIds":{"description":"Optional explicit vendor restriction (RFQ targeting) — same semantics as CreateRfq.vendorIds.","items":{"$ref":"#/components/schemas/Uuid"},"maxItems":100,"minItems":1,"type":"array"}},"type":"object"},"SetAllowances":{"description":"Full replace of an allowance list (one line per product — a duplicate product rejects; an empty list clears it).","properties":{"allowances":{"items":{"$ref":"#/components/schemas/AllowanceInput"},"maxItems":200,"type":"array"}},"required":["allowances"],"type":"object"},"SetMenuPlanSlot":{"properties":{"dayIndex":{"minimum":0,"type":"integer"},"mealSlot":{"enum":["breakfast","mid-morning","lunch","tea","dinner","night"],"type":"string"},"recipeId":{"$ref":"#/components/schemas/Uuid"},"servingsOverride":{"minimum":1,"type":"integer"}},"required":["dayIndex","mealSlot","recipeId"],"type":"object"},"SetNotificationPreference":{"additionalProperties":false,"properties":{"enabled":{"type":"boolean"}},"required":["enabled"],"type":"object"},"SetStockTakeLines":{"description":"Full replace of a draft count's lines (one line per product — a duplicate product rejects).","properties":{"lines":{"items":{"$ref":"#/components/schemas/StockTakeLineInput"},"maxItems":1000,"type":"array"}},"required":["lines"],"type":"object"},"SettlementSummary":{"description":"Per-order goods-settlement rollup over recorded payments (embedded on getOrder).","properties":{"lastRecordedAt":{"format":"date-time","nullable":true,"type":"string"},"outstanding":{"$ref":"#/components/schemas/Money"},"recordedTotal":{"$ref":"#/components/schemas/Money"},"settled":{"type":"boolean"}},"required":["recordedTotal","outstanding","settled"],"type":"object"},"SignInLinkRequest":{"properties":{"email":{"description":"The address the user signs in with. The response is 204 whether or not an account holds it (no enumeration).","maxLength":254,"minLength":3,"type":"string"}},"required":["email"],"type":"object"},"SignedInSession":{"description":"One live identity-provider session of a user. A browser session is created by a portal or the Staff Console at sign-in; an AI-application session is the grant behind a connected AI application (its applicationName comes from the connected-applications register). The facts (surface, ip, userAgent) are recorded by NERMO at session creation; a session created by another login client carries none and shows as kind=other.","properties":{"applicationName":{"description":"For an AI-application session with a live grant, the surface name (Claude, Grok, …).","type":"string"},"expiresAt":{"$ref":"#/components/schemas/Timestamp"},"id":{"description":"The identity provider's session id — the revoke handle.","type":"string"},"ip":{"description":"The signing-in client's IP as recorded at session creation, when known.","type":"string"},"kind":{"enum":["browser","ai-application","other"],"type":"string"},"lastActiveAt":{"allOf":[{"$ref":"#/components/schemas/Timestamp"}],"description":"The identity provider's last change to the session (a token refresh touches it)."},"signedInAt":{"$ref":"#/components/schemas/Timestamp"},"surface":{"description":"The NERMO surface that created the session.","enum":["client-portal","vendor-portal","staff-console","mcp-connect","unknown"],"type":"string"},"userAgent":{"description":"The signing-in client's user agent as recorded at session creation, when known.","type":"string"}},"required":["id","kind","surface","signedInAt","lastActiveAt"],"type":"object"},"SignedInSessionList":{"description":"Unpaginated — a person holds a handful of live sessions.","properties":{"data":{"items":{"$ref":"#/components/schemas/SignedInSession"},"type":"array"}},"required":["data"],"type":"object"},"StaffCounterpartyFlag":{"allOf":[{"$ref":"#/components/schemas/CounterpartyFlag"},{"properties":{"clientName":{"type":"string"},"clientTenantId":{"$ref":"#/components/schemas/Uuid"},"vendorName":{"type":"string"},"vendorTenantId":{"$ref":"#/components/schemas/Uuid"}},"required":["vendorTenantId","vendorName","clientTenantId","clientName"],"type":"object"}]},"StaffSubscriptionAcceptance":{"description":"The staff-surface acceptance: staff act on a written instrument (a signed order form, an email from an authorised client contact) and record its reference; the rate is the account's rate as of today.","properties":{"accepted":{"enum":[true],"type":"boolean"},"reference":{"description":"The written instrument the acceptance rests on.","maxLength":500,"minLength":1,"type":"string"}},"required":["accepted","reference"],"type":"object"},"StaffVendorFeedback":{"allOf":[{"$ref":"#/components/schemas/VendorFeedback"},{"properties":{"clientTenantId":{"$ref":"#/components/schemas/Uuid"},"deliveryId":{"$ref":"#/components/schemas/Uuid"},"deliveryReference":{"type":"string"},"orderId":{"$ref":"#/components/schemas/Uuid"},"orderReference":{"type":"string"},"vendorTenantId":{"$ref":"#/components/schemas/Uuid"}},"required":["clientTenantId","vendorTenantId","deliveryId","deliveryReference","orderId","orderReference"],"type":"object"}]},"StockReceipt":{"description":"The once-per-delivery record that the delivery's received quantities were added to the vessel's stock on board. Client-only (galley.inventory.*) — never embedded on the two-party delivery read.","properties":{"applied":{"minimum":0,"type":"integer"},"deliveryId":{"$ref":"#/components/schemas/Uuid"},"id":{"$ref":"#/components/schemas/Uuid"},"postedAt":{"$ref":"#/components/schemas/Timestamp"},"postedByUserId":{"format":"uuid","nullable":true,"type":"string"},"skipped":{"items":{"$ref":"#/components/schemas/StockReceiptSkippedLine"},"type":"array"},"vesselId":{"$ref":"#/components/schemas/Uuid"}},"required":["id","deliveryId","vesselId","postedAt","applied","skipped"],"type":"object"},"StockReceiptSkippedLine":{"properties":{"deliveryLineId":{"$ref":"#/components/schemas/Uuid"},"reason":{"enum":["no-product","unit-mismatch","counted-after-delivery"],"type":"string"},"sourceText":{"type":"string"}},"required":["deliveryLineId","sourceText","reason"],"type":"object"},"StockTake":{"description":"A dated count. Draft = lines editable (replace-set) and deletable; posted = immutable, the counted products' on-hand figures were set. lines[] is present on getStockTake and every write response.","properties":{"countedAt":{"format":"date","type":"string"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"createdByUserId":{"format":"uuid","nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"lineCount":{"minimum":0,"type":"integer"},"lines":{"description":"Present on getStockTake and every write response.","items":{"$ref":"#/components/schemas/StockTakeLine"},"type":"array"},"notes":{"nullable":true,"type":"string"},"postedAt":{"format":"date-time","nullable":true,"type":"string"},"postedByName":{"nullable":true,"type":"string"},"postedByUserId":{"format":"uuid","nullable":true,"type":"string"},"status":{"enum":["draft","posted"],"type":"string"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"},"vesselId":{"$ref":"#/components/schemas/Uuid"}},"required":["id","vesselId","countedAt","status","lineCount","createdAt","updatedAt"],"type":"object"},"StockTakeLine":{"properties":{"canonicalProductId":{"$ref":"#/components/schemas/Uuid"},"id":{"$ref":"#/components/schemas/Uuid"},"notes":{"nullable":true,"type":"string"},"productName":{"type":"string"},"qty":{"$ref":"#/components/schemas/DecimalString"},"unitCode":{"type":"string"}},"required":["id","canonicalProductId","productName","qty","unitCode"],"type":"object"},"StockTakeLineInput":{"properties":{"canonicalProductId":{"$ref":"#/components/schemas/Uuid"},"notes":{"maxLength":500,"type":"string"},"qty":{"$ref":"#/components/schemas/DecimalString"},"unitCode":{"minLength":1,"type":"string"}},"required":["canonicalProductId","qty","unitCode"],"type":"object"},"StockTakeList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/StockTake"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"StockTakePostResult":{"description":"The posted count plus what posting did — how many lines set a figure and which were skipped (stale-count), so the page can say so.","properties":{"applied":{"minimum":0,"type":"integer"},"skipped":{"items":{"$ref":"#/components/schemas/StockTakeSkippedLine"},"type":"array"},"stockTake":{"$ref":"#/components/schemas/StockTake"}},"required":["stockTake","applied","skipped"],"type":"object"},"StockTakeSkippedLine":{"properties":{"canonicalProductId":{"$ref":"#/components/schemas/Uuid"},"productName":{"type":"string"},"reason":{"enum":["stale-count"],"type":"string"},"stockTakeLineId":{"$ref":"#/components/schemas/Uuid"}},"required":["stockTakeLineId","canonicalProductId","reason"],"type":"object"},"SubmitQuote":{"properties":{"currency":{"$ref":"#/components/schemas/VendorCurrency"},"lines":{"items":{"$ref":"#/components/schemas/SubmitQuoteLine"},"maxItems":500,"minItems":1,"type":"array"},"rfqId":{"$ref":"#/components/schemas/Uuid"},"rfqRoutingId":{"$ref":"#/components/schemas/Uuid"},"terms":{"$ref":"#/components/schemas/PaymentTerms"},"validUntil":{"format":"date-time","type":"string"}},"required":["rfqId","rfqRoutingId","currency","validUntil","lines"],"type":"object"},"SubmitQuoteLine":{"properties":{"canonicalProductId":{"format":"uuid","nullable":true,"type":"string"},"leadTimeDays":{"type":"integer"},"notes":{"type":"string"},"qty":{"$ref":"#/components/schemas/DecimalString"},"rfqLineId":{"$ref":"#/components/schemas/Uuid"},"sourceText":{"type":"string"},"substituteOfferedId":{"format":"uuid","nullable":true,"type":"string"},"unitCode":{"type":"string"},"unitPrice":{"$ref":"#/components/schemas/Money"}},"required":["rfqLineId","qty","unitCode","unitPrice"],"type":"object"},"SubscriptionAcceptance":{"description":"The client's recorded acceptance of the subscription fee for ONE vessel — required on every client-surface add and reactivation (the vessel is the billing unit, so adding one is the opt-in). `rate` is the monthly per-vessel rate the client saw; the server compares it to the account's rate as of today and refuses a mismatch with 409 subscription-rate-changed (refetch the subscription, re-ask). The vessel bills from its activation date, at the lower of this rate and the rate in force then; the first invoice covers the remaining days of the period the activation lands in (fewer than ten days left → those days ride the next monthly invoice as a line instead).","properties":{"accepted":{"description":"Must be true — the explicit tick.","enum":[true],"type":"boolean"},"agreementVersion":{"description":"The Client Agreement version in force when the fee was accepted (GET /company → agreement.currentVersion).","maxLength":40,"minLength":1,"type":"string"},"rate":{"$ref":"#/components/schemas/Money"}},"required":["accepted","rate","agreementVersion"],"type":"object"},"SubscriptionInfo":{"description":"The client's active rate + the current calendar-month period anchor (UTC, half-open). Feeds the add-vessel consent line and the decommission invoiced-through math.","properties":{"currentPeriodEnd":{"format":"date","type":"string"},"currentPeriodStart":{"format":"date","type":"string"},"effectiveFrom":{"format":"date","type":"string"},"monthlyRatePerVessel":{"$ref":"#/components/schemas/Money"}},"required":["monthlyRatePerVessel","effectiveFrom","currentPeriodStart","currentPeriodEnd"],"type":"object"},"SubstituteScope":{"description":"Where a T55 substitute edge is suggested. general = applicable on every surface (the default — NOT \"unspecified\"); procurement = a sourcing fallback (fine to offer or quote) that changes the dish when cooked, so it is hidden from the galley/recipe alternate picker and should not be proposed as a recipe swap.","enum":["general","procurement"],"type":"string"},"TenantScreeningTrail":{"description":"A tenant's full screening trail (newest first), the case detail's shape — every row per screened entity, match details included (staff-only by op placement).","properties":{"data":{"items":{"$ref":"#/components/schemas/OnboardingScreeningResult"},"type":"array"}},"required":["data"],"type":"object"},"Timestamp":{"description":"RFC 3339, UTC.","format":"date-time","type":"string"},"UnitAliasVerdict":{"properties":{"alias":{"type":"string"},"reviewState":{"enum":["approved","rejected"],"type":"string"},"unitCode":{"type":"string"}},"required":["alias","reviewState"],"type":"object"},"UnitObservationCreate":{"properties":{"correctedTo":{"description":"The canonical code the user corrected the raw notation to, if any.","maxLength":16,"type":"string"},"rawText":{"maxLength":64,"minLength":1,"type":"string"},"sourceRef":{"maxLength":64,"type":"string"}},"required":["rawText"],"type":"object"},"UnitObservationQueueItem":{"properties":{"conflicted":{"type":"boolean"},"correctedTo":{"type":"string"},"rawText":{"type":"string"},"tenants":{"type":"integer"},"votes":{"type":"integer"}},"required":["rawText","tenants","votes","conflicted"],"type":"object"},"UnitObservationQueueList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/UnitObservationQueueItem"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"UnitStandard":{"properties":{"aliases":{"description":"Approved alias spellings that fold to this code.","items":{"type":"string"},"type":"array"},"code":{"type":"string"},"dimension":{"enum":["mass","volume","count","length"],"type":"string"},"name":{"type":"string"}},"required":["code","name","dimension","aliases"],"type":"object"},"UnitStandardList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/UnitStandard"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"UnitSuggestion":{"properties":{"code":{"type":"string"},"usageCount":{"type":"integer"}},"required":["code","usageCount"],"type":"object"},"UnitSuggestionList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/UnitSuggestion"},"type":"array"}},"required":["data"],"type":"object"},"UpdateClient":{"description":"All fields optional (minProperties:1). NOTE for the handler: updateClientCore historically derived its 404 SOLELY from UpdateClientName returning 0 rows, so a name-less body must hit an explicit AdminGetClient existence guard or a non-client tenant falls through to a 500 instead of a 404 (TestAdminUpdateClient_NonClientTenant_404).","minProperties":1,"properties":{"city":{"type":"string"},"country":{"type":"string"},"legalEntityType":{"type":"string"},"name":{"minLength":1,"type":"string"},"registeredAddress":{"type":"string"},"registrationNumber":{"type":"string"},"vatNumber":{"type":"string"}},"type":"object"},"UpdateCompanyProfile":{"additionalProperties":false,"description":"The union of both parties' EDITABLE keys. additionalProperties:false is what rejects a Class-I key (they are not declared here) at the validation middleware — the handler has no re-assert because oapi-codegen emits declared properties only and encoding/json discards unknown keys; the handler's real defence is structural (it writes no Class-I column). A key belonging to the other party is a 400 from the runtime ac.Party guard. Absent = unchanged (the UpdateVendorProfile COALESCE idiom); there is no clear-to-null.\nThe three billing_* keys are editable without being Class P, and this is the only write op on the surface, so they land here too.","minProperties":1,"properties":{"billingAddress":{"description":"CLIENT ONLY.","maxLength":2000,"type":"string"},"billingEmail":{"description":"CLIENT ONLY.","maxLength":200,"type":"string"},"billingEntityName":{"description":"CLIENT ONLY.","maxLength":200,"type":"string"},"defaultCurrency":{"allOf":[{"$ref":"#/components/schemas/VendorCurrency"}],"description":"VENDOR ONLY. The vendor's goods currency, self-service (editable without being Class P, the billing_* precedent). A change changes the vendor row only: stored catalogue numerals are NOT repriced (the whole catalogue now reads in the new currency at the stored numbers — reprice or re-import if they are not), open drafts, submitted quotes and orders keep their own stored currency and numbers, and new prefilled drafts use the new currency from the next routing on."},"displayName":{"description":"VENDOR ONLY. not-null column + the public listing orders by it, so a whitespace-only value is rejected by the handler.","maxLength":200,"minLength":1,"type":"string"},"email":{"description":"VENDOR ONLY.","maxLength":200,"type":"string"},"emergencyContactName":{"maxLength":200,"type":"string"},"emergencyContactPhone":{"maxLength":200,"type":"string"},"employeeCount":{"description":"VENDOR ONLY.","maximum":2147483647,"minimum":0,"type":"integer"},"parentCompany":{"description":"VENDOR ONLY.","maxLength":200,"type":"string"},"phone":{"maxLength":200,"type":"string"},"productServiceScope":{"description":"VENDOR ONLY.","maxLength":2000,"type":"string"},"taxOffice":{"description":"VENDOR ONLY.","maxLength":200,"type":"string"},"tradingName":{"maxLength":200,"type":"string"},"website":{"maxLength":200,"type":"string"},"yearEstablished":{"description":"VENDOR ONLY.","maximum":2100,"minimum":1800,"type":"integer"}},"type":"object"},"UpdateCorpusRecipe":{"description":"Full replace (attrs + ingredient replace-set, the updateRfq shape). Corpus rows only; never status. attestedAllergenReview is required by the handler only when the edit changes allergens/dietaryTags on a published row.","properties":{"allergens":{"items":{"enum":["celery","crustaceans","eggs","fish","gluten","lupin","milk","molluscs","mustard","nuts","peanuts","sesame","soybeans","sulphites"],"type":"string"},"type":"array"},"attestedAllergenReview":{"description":"Set true to re-attest a safety-data change on a published corpus recipe (design §4).","type":"boolean"},"category":{"type":"string"},"cookMin":{"minimum":0,"type":"integer"},"cuisine":{"type":"string"},"dietaryTags":{"items":{"enum":["halal","kosher","vegetarian","vegan","pescatarian","no-pork","no-beef"],"type":"string"},"type":"array"},"ingredients":{"items":{"$ref":"#/components/schemas/CreateRecipeIngredient"},"maxItems":200,"minItems":1,"type":"array"},"method":{"type":"string"},"name":{"minLength":1,"type":"string"},"prepMin":{"minimum":0,"type":"integer"},"serves":{"minimum":1,"type":"integer"}},"required":["name","serves","ingredients"],"type":"object"},"UpdateMenuPlan":{"description":"Full replace (attrs + slot replace-set — the grid saves atomically). Draft-only.","properties":{"crewComplement":{"minimum":1,"nullable":true,"type":"integer"},"durationDays":{"minimum":1,"nullable":true,"type":"integer"},"name":{"minLength":1,"type":"string"},"slots":{"items":{"$ref":"#/components/schemas/SetMenuPlanSlot"},"maxItems":500,"type":"array"},"startDate":{"format":"date","nullable":true,"type":"string"},"vesselId":{"format":"uuid","nullable":true,"type":"string"}},"required":["name","slots"],"type":"object"},"UpdateMenuTemplate":{"description":"Full replace (attrs + slot replace-set). Draft-only.","properties":{"allowances":{"items":{"$ref":"#/components/schemas/AllowanceInput"},"maxItems":200,"type":"array"},"crewProfile":{"enum":["filipino","indian","chinese","indonesian","ukrainian-eastern-european","western-northern-european","south-american"],"nullable":true,"type":"string"},"durationDays":{"maximum":60,"minimum":1,"nullable":true,"type":"integer"},"name":{"minLength":1,"type":"string"},"slots":{"items":{"$ref":"#/components/schemas/SetMenuPlanSlot"},"maxItems":500,"type":"array"}},"required":["name","slots"],"type":"object"},"UpdateRecipe":{"description":"Full replace (attrs + ingredient replace-set, the updateRfq shape). Own rows only; never status.","properties":{"allergens":{"items":{"enum":["celery","crustaceans","eggs","fish","gluten","lupin","milk","molluscs","mustard","nuts","peanuts","sesame","soybeans","sulphites"],"type":"string"},"type":"array"},"category":{"type":"string"},"cookMin":{"minimum":0,"type":"integer"},"cuisine":{"type":"string"},"dietaryTags":{"items":{"enum":["halal","kosher","vegetarian","vegan","pescatarian","no-pork","no-beef"],"type":"string"},"type":"array"},"ingredients":{"items":{"$ref":"#/components/schemas/CreateRecipeIngredient"},"maxItems":200,"minItems":1,"type":"array"},"method":{"type":"string"},"name":{"minLength":1,"type":"string"},"prepMin":{"minimum":0,"type":"integer"},"serves":{"minimum":1,"type":"integer"}},"required":["name","serves","ingredients"],"type":"object"},"UpdateRfq":{"properties":{"deliveryLocation":{"enum":["berth","anchorage","unknown"],"type":"string"},"eta":{"format":"date-time","type":"string"},"lines":{"items":{"$ref":"#/components/schemas/CreateRfqLine"},"maxItems":500,"type":"array"},"notes":{"type":"string"},"portId":{"$ref":"#/components/schemas/Uuid"},"vesselId":{"$ref":"#/components/schemas/Uuid"}},"required":["vesselId","portId","deliveryLocation","lines"],"type":"object"},"UpdateRole":{"description":"Flat partial update (PATCH). Each field is optional, but at least one must be provided (an empty body is rejected).","minProperties":1,"properties":{"name":{"type":"string"},"permissionKeys":{"items":{"type":"string"},"type":"array"}},"type":"object"},"UpdateUser":{"description":"Partial update (PATCH). Each field is optional, but at least one must be provided (an empty body is rejected); status + credentials have dedicated routes.","minProperties":1,"properties":{"displayName":{"type":"string"},"email":{"format":"email","type":"string"},"roleCode":{"type":"string"},"vesselAssignments":{"items":{"$ref":"#/components/schemas/Uuid"},"type":"array"}},"type":"object"},"UpdateVendor":{"description":"Flat partial update (PATCH). Each field is optional, but at least one must be provided (an empty body is rejected); no transition guards on status.","minProperties":1,"properties":{"city":{"type":"string"},"country":{"type":"string"},"defaultCurrency":{"$ref":"#/components/schemas/VendorCurrency"},"displayName":{"description":"Renames the vendor AND the tenants anchor (counterparty refs + the public listing render it), so a whitespace-only value is rejected by the handler.","minLength":1,"type":"string"},"legalEntityType":{"type":"string"},"legalName":{"type":"string"},"primaryPortId":{"description":"v0: set/change only — null is rejected (cannot clear the primary port via PATCH); omit to leave unchanged.","format":"uuid","type":"string"},"registeredAddress":{"type":"string"},"registrationNumber":{"type":"string"},"status":{"enum":["intake","kyc-pending","active","suspended","terminated"],"type":"string"},"trustedFastTrack":{"type":"boolean"},"vatNumber":{"type":"string"}},"type":"object"},"UpdateVessel":{"description":"Client metadata patch — at least one field; NO imo, NO status.","minProperties":1,"properties":{"callSign":{"type":"string"},"flagCountry":{"maxLength":3,"minLength":3,"type":"string"},"name":{"maxLength":200,"minLength":1,"type":"string"},"vesselType":{"enum":["barge","bulk-carrier","container-ship","cruise","drillship","ferry","fpso","lng-carrier","lpg-carrier","offshore","ro-ro","tanker","tug"],"type":"string"}},"type":"object"},"UpdateVesselGroup":{"description":"Partial update (PATCH). Each field is optional, but at least one must be provided (an empty body is rejected).","minProperties":1,"properties":{"name":{"type":"string"},"vesselIds":{"items":{"$ref":"#/components/schemas/Uuid"},"type":"array"}},"type":"object"},"UpsertProductSubstitute":{"properties":{"rank":{"maximum":2147483647,"minimum":1,"type":"integer"},"scope":{"$ref":"#/components/schemas/SubstituteScope"}},"required":["rank","scope"],"type":"object"},"UpsertVendorCoverage":{"properties":{"active":{"default":true,"type":"boolean"},"costCategories":{"items":{"$ref":"#/components/schemas/CostCategory"},"minItems":1,"type":"array"}},"required":["costCategories"],"type":"object"},"User":{"properties":{"createdAt":{"$ref":"#/components/schemas/Timestamp"},"displayName":{"type":"string"},"email":{"format":"email","type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"roleCode":{"description":"Code of the single role assigned to the user (Phase 1 = one role per user).","type":"string"},"status":{"enum":["active","deactivated"],"type":"string"},"tenantId":{"$ref":"#/components/schemas/Uuid"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"},"vesselAssignments":{"description":"Vessel ids this user is scoped to (empty for all-vessels/own-orders scopes).","items":{"$ref":"#/components/schemas/Uuid"},"type":"array"}},"required":["id","tenantId","email","displayName","roleCode","status","vesselAssignments","createdAt","updatedAt"],"type":"object"},"UserList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/User"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"Uuid":{"example":"018f4e2a-1c3b-7a9e-bf21-2d6b9a0c4e11","format":"uuid","type":"string"},"VendorApplicationRequest":{"properties":{"company":{"properties":{"city":{"minLength":1,"type":"string"},"country":{"minLength":1,"type":"string"},"legalEntityType":{"type":"string"},"legalName":{"minLength":1,"type":"string"},"registeredAddress":{"minLength":1,"type":"string"},"registrationNumber":{"minLength":1,"type":"string"},"vatNumber":{"type":"string"},"website":{"type":"string"},"yearEstablished":{"maximum":2100,"minimum":1800,"type":"integer"}},"required":["legalName","country","registeredAddress","city","registrationNumber"],"type":"object"},"compliance":{"description":"Declarations only — document uploads happen in-portal post-mint.","properties":{"certifications":{"items":{"type":"string"},"type":"array"},"kycDocsAvailable":{"type":"boolean"}},"type":"object"},"contact":{"$ref":"#/components/schemas/ApplicationContact"},"coverage":{"description":"Declaration — snapshot only, seeds the in-portal structured coverage step; never written to vendor_port_coverage from here.","properties":{"categories":{"items":{"type":"string"},"type":"array"},"deliveryMethods":{"items":{"type":"string"},"type":"array"},"leadTime":{"type":"string"},"portsServed":{"description":"UN/LOCODEs of the ports the vendor serves (picked, not typed).","items":{"maxLength":5,"minLength":5,"pattern":"^[A-Z0-9]{5}$","type":"string"},"type":"array"},"regions":{"items":{"type":"string"},"type":"array"}},"type":"object"},"defaultCurrency":{"$ref":"#/components/schemas/VendorCurrency"},"owners":{"items":{"$ref":"#/components/schemas/ApplicationOwner"},"minItems":1,"type":"array"},"renderedAt":{"format":"int64","type":"integer"},"supplierTerms":{"description":"Acceptance of the full NERMO supplier terms (ABC / sanctions / no-counterfeit / CTM prohibition / invoice-in-order-currency; versioned).","properties":{"accepted":{"description":"Must be true (handler guard).","type":"boolean"},"version":{"minLength":1,"type":"string"}},"required":["version","accepted"],"type":"object"},"terms":{"$ref":"#/components/schemas/ApplicationTerms"},"turnstileToken":{"type":"string"},"website":{"description":"Honeypot — must be empty.","maxLength":0,"type":"string"}},"required":["terms","company","contact","defaultCurrency","owners","supplierTerms","renderedAt"],"type":"object"},"VendorCatalogItem":{"properties":{"active":{"type":"boolean"},"availability":{"enum":["in-stock","limited","out-of-stock"],"nullable":true,"type":"string"},"canonicalProductId":{"format":"uuid","nullable":true,"type":"string"},"canonicalProductName":{"nullable":true,"type":"string"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"description":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"images":{"description":"Read-only gallery (max 5, one primary; upload/serving ride the non-contract routes).","items":{"$ref":"#/components/schemas/VendorCatalogItemImage"},"type":"array"},"leadTimeDays":{"nullable":true,"type":"integer"},"name":{"type":"string"},"packQty":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"},"packUnitCode":{"nullable":true,"type":"string"},"stockQty":{"nullable":true,"pattern":"^-?\\d+(\\.\\d+)?$","type":"string"},"unitCode":{"type":"string"},"unitPrice":{"$ref":"#/components/schemas/DecimalString"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"},"vendorSku":{"nullable":true,"type":"string"}},"required":["id","name","unitCode","unitPrice","active","images","createdAt","updatedAt"],"type":"object"},"VendorCatalogItemImage":{"properties":{"byteSize":{"type":"integer"},"contentType":{"enum":["image/jpeg","image/png","image/webp"],"type":"string"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"id":{"$ref":"#/components/schemas/Uuid"},"isPrimary":{"type":"boolean"},"sortOrder":{"type":"integer"}},"required":["id","contentType","byteSize","sortOrder","isPrimary","createdAt"],"type":"object"},"VendorCatalogItemImageUpdate":{"properties":{"isPrimary":{"type":"boolean"}},"required":["isPrimary"],"type":"object"},"VendorCatalogItemInput":{"properties":{"active":{"default":true,"type":"boolean"},"availability":{"enum":["in-stock","limited","out-of-stock"],"type":"string"},"canonicalProductId":{"format":"uuid","nullable":true,"type":"string"},"description":{"type":"string"},"leadTimeDays":{"type":"integer"},"name":{"minLength":1,"type":"string"},"packQty":{"$ref":"#/components/schemas/DecimalString"},"packUnitCode":{"type":"string"},"stockQty":{"$ref":"#/components/schemas/DecimalString"},"unitCode":{"type":"string"},"unitPrice":{"$ref":"#/components/schemas/DecimalString"},"vendorSku":{"type":"string"}},"required":["name","unitCode","unitPrice"],"type":"object"},"VendorCatalogItemList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/VendorCatalogItem"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"VendorCoverage":{"properties":{"active":{"type":"boolean"},"costCategories":{"items":{"$ref":"#/components/schemas/CostCategory"},"type":"array"},"portId":{"$ref":"#/components/schemas/Uuid"},"portName":{"type":"string"},"unlocode":{"maxLength":5,"minLength":5,"type":"string"}},"required":["portId","portName","unlocode","costCategories","active"],"type":"object"},"VendorCoverageList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/VendorCoverage"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"VendorCoverageOwnList":{"description":"The vendor's own coverage — unpaginated (a vendor covers a handful of ports).","properties":{"data":{"items":{"$ref":"#/components/schemas/VendorCoverage"},"type":"array"}},"required":["data"],"type":"object"},"VendorCurrency":{"description":"The vendor's catalog / quote / order currency, and a vessel budget's currency — any currency NERMO can settle its USD rebate in: the ECB reference-rate set, the fixed USD pegs, and EUR. The vendor prices, quotes and is paid in its own currency; the client never sets one; NERMO itself is paid in USD only.","enum":["AED","AUD","BHD","BRL","BSD","BZD","CAD","CHF","CNY","CZK","DJF","DKK","EUR","GBP","HKD","HUF","IDR","ILS","INR","ISK","JOD","JPY","KRW","MXN","MYR","NOK","NZD","OMR","PAB","PHP","PLN","QAR","RON","SAR","SEK","SGD","THB","TRY","USD","XCD","ZAR"],"type":"string"},"VendorDetail":{"properties":{"city":{"nullable":true,"type":"string"},"country":{"description":"ISO 3166-1 alpha-3 in practice; unconstrained in the column.","nullable":true,"type":"string"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"defaultCurrency":{"$ref":"#/components/schemas/VendorCurrency"},"directoryConsentedAt":{"description":"When the vendor recorded public-listing consent (null = never; nothing publishes without it).","format":"date-time","nullable":true,"type":"string"},"directoryPublished":{"description":"T8 public-page serving flag (vendor consent auto-publishes when eligible; staff publish/unpublish is the override — publish requires consent + active + latest tenant screening clear).","type":"boolean"},"directorySlug":{"description":"Public page slug; minted on first publish, stable thereafter (kept across unpublish).","nullable":true,"type":"string"},"displayName":{"type":"string"},"email":{"nullable":true,"type":"string"},"emergencyContactName":{"nullable":true,"type":"string"},"emergencyContactPhone":{"nullable":true,"type":"string"},"employeeCount":{"nullable":true,"type":"integer"},"goodsTerms":{"$ref":"#/components/schemas/PaymentTerms"},"legalEntityType":{"nullable":true,"type":"string"},"legalName":{"type":"string"},"loginEnabled":{"description":"True iff tenants.status='active' — the login kill-switch, orthogonal to the lifecycle status.","type":"boolean"},"parentCompany":{"nullable":true,"type":"string"},"phone":{"nullable":true,"type":"string"},"primaryPortId":{"format":"uuid","nullable":true,"type":"string"},"productServiceScope":{"nullable":true,"type":"string"},"registeredAddress":{"nullable":true,"type":"string"},"registrationNumber":{"nullable":true,"type":"string"},"status":{"description":"Vendor lifecycle (vendor_status). Gates submitQuote; does NOT affect login.","enum":["intake","kyc-pending","active","suspended","terminated"],"type":"string"},"taxOffice":{"nullable":true,"type":"string"},"tenantId":{"$ref":"#/components/schemas/Uuid"},"tradingName":{"nullable":true,"type":"string"},"trustedFastTrack":{"description":"When true, a kyc-pending vendor may still submit quotes (the quote-gate override).","type":"boolean"},"userCount":{"minimum":0,"type":"integer"},"vatNumber":{"nullable":true,"type":"string"},"website":{"nullable":true,"type":"string"},"yearEstablished":{"nullable":true,"type":"integer"}},"required":["tenantId","legalName","displayName","status","loginEnabled","defaultCurrency","trustedFastTrack","directoryPublished","userCount","createdAt"],"type":"object"},"VendorDirectoryCatalog":{"properties":{"data":{"items":{"$ref":"#/components/schemas/VendorDirectoryCatalogItem"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"},"unmappedCount":{"type":"integer"}},"required":["data","unmappedCount","pagination"],"type":"object"},"VendorDirectoryCatalogItem":{"properties":{"availability":{"enum":["in-stock","limited","out-of-stock"],"nullable":true,"type":"string"},"canonicalProductId":{"$ref":"#/components/schemas/Uuid"},"categoryId":{"format":"uuid","nullable":true,"type":"string"},"categoryName":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"leadTimeDays":{"nullable":true,"type":"integer"},"name":{"type":"string"},"packQty":{"nullable":true,"pattern":"^\\d+(\\.\\d+)?$","type":"string"},"packUnitCode":{"nullable":true,"type":"string"},"unitCode":{"type":"string"}},"required":["id","canonicalProductId","name","unitCode"],"type":"object"},"VendorDirectoryCertification":{"properties":{"docType":{"$ref":"#/components/schemas/ComplianceDocType"},"status":{"enum":["verified","expiring","expired"],"type":"string"}},"required":["docType","status"],"type":"object"},"VendorDirectoryCoverage":{"properties":{"costCategories":{"items":{"$ref":"#/components/schemas/CostCategory"},"type":"array"},"portId":{"$ref":"#/components/schemas/Uuid"},"portName":{"type":"string"},"unlocode":{"maxLength":5,"minLength":5,"type":"string"}},"required":["portId","portName","unlocode","costCategories"],"type":"object"},"VendorDirectoryEntry":{"description":"One directory search hit.","properties":{"catalogMatches":{"type":"integer"},"costCategories":{"items":{"$ref":"#/components/schemas/CostCategory"},"type":"array"},"defaultCurrency":{"$ref":"#/components/schemas/VendorCurrency"},"displayName":{"type":"string"},"primaryPort":{"$ref":"#/components/schemas/PortRef"},"primaryPortId":{"format":"uuid","nullable":true,"type":"string"},"tenantId":{"$ref":"#/components/schemas/Uuid"}},"required":["tenantId","displayName","defaultCurrency"],"type":"object"},"VendorDirectoryList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/VendorDirectoryEntry"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"VendorDirectoryProfile":{"properties":{"certifications":{"items":{"$ref":"#/components/schemas/VendorDirectoryCertification"},"type":"array"},"coverage":{"items":{"$ref":"#/components/schemas/VendorDirectoryCoverage"},"type":"array"},"defaultCurrency":{"$ref":"#/components/schemas/VendorCurrency"},"displayName":{"type":"string"},"primaryPort":{"$ref":"#/components/schemas/PortRef"},"primaryPortId":{"format":"uuid","nullable":true,"type":"string"},"tenantId":{"$ref":"#/components/schemas/Uuid"}},"required":["tenantId","displayName","defaultCurrency","coverage","certifications"],"type":"object"},"VendorFeedback":{"properties":{"categories":{"items":{"enum":["wrong-product","expired-goods","short-delivery","late-delivery","poor-handling","conduct","other"],"type":"string"},"minItems":1,"type":"array"},"createdAt":{"format":"date-time","type":"string"},"createdByUserId":{"$ref":"#/components/schemas/Uuid"},"id":{"$ref":"#/components/schemas/Uuid"},"note":{"nullable":true,"type":"string"}},"required":["id","categories","createdByUserId","createdAt"],"type":"object"},"VendorFeedbackInput":{"properties":{"categories":{"items":{"enum":["wrong-product","expired-goods","short-delivery","late-delivery","poor-handling","conduct","other"],"type":"string"},"minItems":1,"type":"array"},"note":{"type":"string"}},"required":["categories"],"type":"object"},"VendorList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/VendorDetail"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"VendorListing":{"properties":{"consented":{"type":"boolean"},"consentedAt":{"format":"date-time","nullable":true,"type":"string"},"eligible":{"description":"True when consent would publish (or keep published) the listing right now.","type":"boolean"},"published":{"type":"boolean"},"slug":{"description":"The public URL slug once minted (nermo.io/suppliers/{slug}); stable across unpublish/republish.","nullable":true,"type":"string"}},"required":["consented","published","eligible"],"type":"object"},"VendorOnboardingChecklist":{"properties":{"bankingEntered":{"type":"boolean"},"bankingVerified":{"description":"Staff payee-name-match verification recorded (verify-banking).","type":"boolean"},"coverageActive":{"type":"boolean"},"coverageCount":{"minimum":0,"type":"integer"},"docsCount":{"minimum":0,"type":"integer"},"docsUploaded":{"type":"boolean"},"kycPartiesCount":{"minimum":0,"type":"integer"},"kycPartiesPresent":{"type":"boolean"},"portsDeclared":{"description":"The application's declared port UN/LOCODEs — a prefill hint for the coverage picker. Null for staff-provisioned vendors (no application snapshot).","items":{"type":"string"},"nullable":true,"type":"array"},"termsAccepted":{"type":"boolean"},"vendorStatus":{"enum":["intake","kyc-pending","active","suspended","terminated"],"type":"string"}},"required":["vendorStatus","termsAccepted","docsUploaded","docsCount","kycPartiesPresent","kycPartiesCount","bankingEntered","bankingVerified","coverageActive","coverageCount"],"type":"object"},"VendorPaymentMethod":{"properties":{"accountHolder":{"description":"The account holder name, in full (cleartext — for a company account it is the vendor's registered legal name). Null only for accounts entered before 2026-09-02, which hold the name as ciphertext until re-entered.","nullable":true,"type":"string"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"currency":{"$ref":"#/components/schemas/CurrencyCode"},"ibanLast4":{"nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"isDefault":{"type":"boolean"},"status":{"enum":["pending","verified","disabled"],"type":"string"},"type":{"enum":["bank","stripe-connect"],"type":"string"}},"required":["id","type","currency","status","isDefault","createdAt"],"type":"object"},"VendorPaymentMethodList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/VendorPaymentMethod"},"type":"array"}},"required":["data"],"type":"object"},"VendorRebate":{"description":"One accrual. rebateAmount is the contractual figure in the order currency; settlementAmount is what NERMO invoices — always USD (founder ruling 2026-09-04), converted at accrual at the rate recorded for the delivery date — the ECB euro reference rate, or the fixed USD peg for a pegged currency (fxRate / fxRateDate; absent on a USD order, where the two amounts are equal).","properties":{"accruedAt":{"$ref":"#/components/schemas/Timestamp"},"fxRate":{"description":"USD per 1 unit of the order currency, 8 dp — the ECB reference rate, or the fixed USD peg for a pegged currency. Absent on USD orders.","type":"string"},"fxRateDate":{"description":"The publication date the rate is keyed to (an ECB publication date; a pegged currency's row is materialized on the same dates).","format":"date","type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"invoiceId":{"format":"uuid","nullable":true,"type":"string"},"orderId":{"$ref":"#/components/schemas/Uuid"},"orderReference":{"type":"string"},"ratePct":{"$ref":"#/components/schemas/DecimalString"},"rebateAmount":{"$ref":"#/components/schemas/Money"},"settlementAmount":{"$ref":"#/components/schemas/Money"},"status":{"enum":["accrued","invoiced","settled","waived"],"type":"string"},"transactionValue":{"$ref":"#/components/schemas/Money"}},"required":["id","orderId","orderReference","transactionValue","ratePct","rebateAmount","settlementAmount","status","accruedAt"],"type":"object"},"VendorRebateAgreement":{"properties":{"createdAt":{"$ref":"#/components/schemas/Timestamp"},"effectiveFrom":{"format":"date","type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"paymentTermsDays":{"minimum":0,"type":"integer"},"ratePct":{"$ref":"#/components/schemas/DecimalString"},"source":{"description":"Provenance-of-rate (which schedule governs) — staff = negotiated, sovereign until re-enroll.","enum":["seed","ladder","staff"],"type":"string"},"tier":{"enum":["base","mid","complete"],"nullable":true,"type":"string"}},"required":["id","ratePct","paymentTermsDays","effectiveFrom","createdAt"],"type":"object"},"VendorRebateAgreementList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/VendorRebateAgreement"},"type":"array"},"negotiated":{"description":"Server-computed R-4 sovereignty: the latest agreement row BY WRITE (created_at, whole history — never derivable from one page) is source=staff. Drives the staff 'Return to ladder' affordance.","type":"boolean"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination","negotiated"],"type":"object"},"VendorRebateList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/VendorRebate"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"},"summary":{"description":"Whole-tenant running totals in the settlement currency — one USD row, not just this page.","items":{"$ref":"#/components/schemas/RebateCurrencySummary"},"type":"array"}},"required":["data","pagination","summary"],"type":"object"},"VendorRef":{"description":"Nested read reference to a vendor tenant (no CRUD surface in this cut).","properties":{"displayName":{"type":"string"},"tenantId":{"$ref":"#/components/schemas/Uuid"}},"required":["tenantId","displayName"],"type":"object"},"VendorTerms":{"description":"The vendor's default goods terms (vendors.goods_terms_*).","properties":{"terms":{"$ref":"#/components/schemas/PaymentTerms"}},"required":["terms"],"type":"object"},"VerifyComplianceDocumentRequest":{"properties":{"expiresAt":{"format":"date","nullable":true,"type":"string"},"issuedAt":{"format":"date","nullable":true,"type":"string"},"notes":{"type":"string"}},"type":"object"},"Vessel":{"properties":{"callSign":{"nullable":true,"type":"string"},"createdAt":{"$ref":"#/components/schemas/Timestamp"},"declinedReason":{"allOf":[{"$ref":"#/components/schemas/VesselDeclinedReason"}],"description":"The client-visible reason on a declined vessel; absent on every other status. Never the screening detail."},"decommissionScheduledFor":{"description":"Staff-owned standing cutoff. Origination stops at this instant even before the status flips; cleared only by the staff cancel op or the sweep.","format":"date-time","nullable":true,"type":"string"},"flagCountry":{"description":"ISO 3166-1 alpha-3.","nullable":true,"type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"imo":{"description":"IMO number; nullable (non-IMO craft) and not the key. Immutable post-create on the client surface; staff may correct in place.","nullable":true,"type":"string"},"invoicedThrough":{"description":"Derived — period_end of the latest subscription invoice whose lines cover this vessel (any non-void status). Invoiced, NOT paid: the subscription is billed in arrears, so an invoiced period may still be outstanding. Null until first invoiced. The decommission default date.","format":"date","nullable":true,"type":"string"},"name":{"type":"string"},"pendingReason":{"allOf":[{"$ref":"#/components/schemas/VesselPendingReason"}],"description":"Why a pending vessel is still pending; absent on every other status. screening = the activation screen is in flight, nothing for the client to do. awaiting-agreement = the account has no Client Agreement version in force, so activation is held until it accepts (only a staff add reaches this; the client doors require the current version at the add)."},"status":{"description":"pending = not yet activated after an add or reactivation (no charge, no origination) — pendingReason says whether the activation screen is in flight or the account has accepted no Client Agreement version; active = in service and billed; declined = a screening hit dispositioned by staff (terminal on the client surface — cannot originate, not billed, not client-reactivatable; the client-visible reason is declinedReason). Only the staff reactivate op returns a declined vessel, as a fresh pending acceptance.","enum":["pending","active","declined","decommissioned","transferred"],"type":"string"},"subscriptionAcceptance":{"allOf":[{"$ref":"#/components/schemas/VesselSubscriptionAcceptance"}],"description":"The latest recorded fee acceptance behind this vessel's subscription (absent only for a vessel with none on record)."},"updatedAt":{"$ref":"#/components/schemas/Timestamp"},"vesselType":{"description":"One of the vessels_vessel_type_check set (barge … tug); null = unspecified.","nullable":true,"type":"string"}},"required":["id","name","status","createdAt","updatedAt"],"type":"object"},"VesselBudget":{"properties":{"createdAt":{"$ref":"#/components/schemas/Timestamp"},"crewCount":{"minimum":1,"type":"integer"},"effectiveFrom":{"format":"date","type":"string"},"id":{"$ref":"#/components/schemas/Uuid"},"mandayRate":{"$ref":"#/components/schemas/Money"},"vesselId":{"$ref":"#/components/schemas/Uuid"},"vesselImo":{"nullable":true,"type":"string"}},"required":["id","vesselId","mandayRate","crewCount","effectiveFrom","createdAt"],"type":"object"},"VesselBudgetList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/VesselBudget"},"type":"array"},"missingCount":{"description":"ACTIVE vessels in this tenant still missing a budget row (matches ClientDetail.missingBudgetCount).","minimum":0,"type":"integer"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination","missingCount"],"type":"object"},"VesselDeclinedReason":{"description":"The fixed client-facing vocabulary for a declined vessel (no free text, so a sanctions-derived decline never tips the client off). Staff keep the detail in the screening event.","enum":["identity-not-verified","not-eligible-for-service","withdrawn-by-client","other-contact-support"],"type":"string"},"VesselGroup":{"properties":{"createdAt":{"$ref":"#/components/schemas/Timestamp"},"id":{"$ref":"#/components/schemas/Uuid"},"name":{"type":"string"},"tenantId":{"$ref":"#/components/schemas/Uuid"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"},"vesselIds":{"items":{"$ref":"#/components/schemas/Uuid"},"type":"array"}},"required":["id","tenantId","name","vesselIds","createdAt","updatedAt"],"type":"object"},"VesselGroupList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/VesselGroup"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"VesselList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/Vessel"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["data","pagination"],"type":"object"},"VesselPendingReason":{"description":"Derived, read-only. Present only while the vessel is pending.","enum":["screening","awaiting-agreement"],"type":"string"},"VesselRef":{"description":"Nested read reference to a vessel. Party-scoped at read — resolved from the client's own vessels row or the vendor's write-once dispatch snapshot, whichever the session's RLS admits.","properties":{"imo":{"nullable":true,"type":"string"},"name":{"type":"string"}},"required":["name"],"type":"object"},"VesselStockList":{"properties":{"data":{"items":{"$ref":"#/components/schemas/VesselStockRow"},"type":"array"},"pagination":{"$ref":"#/components/schemas/Pagination"},"summary":{"$ref":"#/components/schemas/VesselStockSummary"}},"required":["data","summary","pagination"],"type":"object"},"VesselStockRow":{"description":"One product on a vessel's stock-on-board list — the current on-hand figure in the row's unit of record (the product's standard unit when it has one, else the unit the first count or receipt arrived in).","properties":{"asOf":{"$ref":"#/components/schemas/Timestamp"},"canonicalProductId":{"$ref":"#/components/schemas/Uuid"},"id":{"$ref":"#/components/schemas/Uuid"},"lastChangeRef":{"$ref":"#/components/schemas/Uuid"},"lastChangeSource":{"enum":["count","receipt"],"type":"string"},"lastCountedAt":{"format":"date","nullable":true,"type":"string"},"productName":{"type":"string"},"productStatus":{"enum":["active","deprecated"],"type":"string"},"qty":{"$ref":"#/components/schemas/DecimalString"},"unitCode":{"type":"string"},"updatedAt":{"$ref":"#/components/schemas/Timestamp"},"vesselId":{"$ref":"#/components/schemas/Uuid"}},"required":["id","vesselId","canonicalProductId","productName","productStatus","qty","unitCode","lastCountedAt","asOf","lastChangeSource","lastChangeRef","updatedAt"],"type":"object"},"VesselStockSummary":{"description":"The generate dialog's two facts — how many products are on the list and the oldest count date (null when nothing has been counted yet).","properties":{"oldestCountedAt":{"format":"date","nullable":true,"type":"string"},"productCount":{"minimum":0,"type":"integer"}},"required":["productCount","oldestCountedAt"],"type":"object"},"VesselSubscriptionAcceptance":{"description":"One recorded fee acceptance (the evidence a subscription invoice rests on).","properties":{"acceptedAt":{"$ref":"#/components/schemas/Timestamp"},"acceptedBy":{"description":"The accepting actor's user id (null for a legacy backfill row).","format":"uuid","nullable":true,"type":"string"},"acceptedByEmail":{"description":"The accepting user's email when the actor is a user of the tenant.","nullable":true,"type":"string"},"agreementVersion":{"nullable":true,"type":"string"},"rate":{"$ref":"#/components/schemas/Money"},"reason":{"enum":["vessel-add","reactivation"],"type":"string"},"reference":{"description":"The written instrument (staff source) or the backfill marker (legacy). Staff reads only — the client party's vessel reads omit it.","nullable":true,"type":"string"},"source":{"enum":["portal","mcp","staff","legacy"],"type":"string"}},"required":["acceptedAt","rate","source","reason"],"type":"object"},"VoidInvoice":{"properties":{"reason":{"minLength":1,"type":"string"}},"required":["reason"],"type":"object"},"WaiveVendorRebate":{"properties":{"reason":{"minLength":1,"type":"string"}},"required":["reason"],"type":"object"},"WebhookDelivery":{"properties":{"attemptNo":{"type":"integer"},"createdAt":{"format":"date-time","type":"string"},"deadLettered":{"type":"boolean"},"durationMs":{"nullable":true,"type":"integer"},"eventId":{"$ref":"#/components/schemas/Uuid"},"id":{"$ref":"#/components/schemas/Uuid"},"nextRetryAt":{"format":"date-time","nullable":true,"type":"string"},"responseBody":{"description":"Capped receiver response — never signature material.","nullable":true,"type":"string"},"responseStatus":{"nullable":true,"type":"integer"},"signedTimestamp":{"format":"date-time","nullable":true,"type":"string"}},"required":["id","eventId","attemptNo","deadLettered","createdAt"],"type":"object"},"WebhookDeliveryList":{"description":"Delivery history is retention-bounded (settled 30d, dead-lettered 90d); older attempts are pruned and never listed.\n","properties":{"items":{"items":{"$ref":"#/components/schemas/WebhookDelivery"},"type":"array"},"page":{"type":"integer"},"pageSize":{"type":"integer"}},"required":["items","page","pageSize"],"type":"object"},"WebhookSecret":{"properties":{"secret":{"description":"Hex signing secret — shown once. The previous secret keeps co-signing until the rotation window closes.","type":"string"}},"required":["secret"],"type":"object"},"WebhookSubscription":{"properties":{"active":{"type":"boolean"},"createdAt":{"format":"date-time","type":"string"},"eventTypes":{"items":{"type":"string"},"minItems":1,"type":"array"},"id":{"$ref":"#/components/schemas/Uuid"},"label":{"maxLength":200,"nullable":true,"type":"string"},"rotationOpen":{"description":"A dual-secret rotation window is open (the previous secret still co-signs).","type":"boolean"},"secretRotatedAt":{"format":"date-time","nullable":true,"type":"string"},"url":{"type":"string"},"verified":{"description":"Whether the endpoint completed the URL-verification handshake; unverified subscriptions receive no deliveries.","type":"boolean"},"verifiedAt":{"format":"date-time","nullable":true,"type":"string"}},"required":["id","url","eventTypes","active","verified","rotationOpen","createdAt"],"type":"object"},"WebhookSubscriptionCreate":{"properties":{"eventTypes":{"items":{"type":"string"},"maxItems":50,"minItems":1,"type":"array"},"label":{"maxLength":200,"nullable":true,"type":"string"},"url":{"maxLength":2000,"minLength":1,"type":"string"}},"required":["url","eventTypes"],"type":"object"},"WebhookSubscriptionCreated":{"properties":{"secret":{"description":"Hex signing secret — returned exactly once, never readable again.","type":"string"},"subscription":{"$ref":"#/components/schemas/WebhookSubscription"}},"required":["subscription","secret"],"type":"object"},"WebhookSubscriptionList":{"properties":{"items":{"items":{"$ref":"#/components/schemas/WebhookSubscription"},"type":"array"}},"required":["items"],"type":"object"},"WebhookSubscriptionUpdate":{"properties":{"active":{"type":"boolean"},"eventTypes":{"items":{"type":"string"},"maxItems":50,"minItems":1,"type":"array"},"label":{"maxLength":200,"nullable":true,"type":"string","x-go-type":"nullable.Nullable[string]","x-go-type-import":{"path":"github.com/oapi-codegen/nullable"},"x-go-type-skip-optional-pointer":true},"url":{"description":"Changing the URL resets verification — a fresh challenge is sent to the new endpoint.","maxLength":2000,"minLength":1,"type":"string"}},"type":"object"}},"securitySchemes":{"bearerAuth":{"bearerFormat":"JWT","description":"Zitadel-issued OIDC JWT. RBAC enforced server-side; required permission keys are noted per operation.","scheme":"bearer","type":"http"}}},"info":{"description":"Canonical API contract for NERMO. MCP and the portals are clients of this contract.","title":"NERMO API","version":"0.1.0"},"openapi":"3.0.3","paths":{"/audit-log":{"get":{"operationId":"ListAuditEvents","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"$ref":"#/components/parameters/DateFromParam"},{"$ref":"#/components/parameters/DateToParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditEventList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Tenant-scoped audit event log. Requires admin.audit.view.","tags":["identity"]}},"/auth/sign-in-link":{"post":{"operationId":"RequestSignInLink","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignInLinkRequest"}}},"required":true},"responses":{"204":{"description":"accepted — a mail is sent if, and only if, an account holds the address"},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[],"summary":"Request a sign-in link for an email address. Public — rate-limited. Always 204 for a well-formed request (unknown address, inactive user, IdP-unknown user alike — no enumeration); the API branches on the account state — a password holder gets the IdP's branded reset mail onto the portal reset page, a never-activated account gets a fresh activation link.","tags":["auth"]}},"/canonical-products":{"get":{"operationId":"ListCanonicalProducts","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Text search over product name, IMPA code, or an approved synonym.","in":"query","name":"q","schema":{"maxLength":200,"type":"string"}},{"description":"Filter to a single product category (see GET /product-categories).","in":"query","name":"categoryId","schema":{"$ref":"#/components/schemas/Uuid"}},{"description":"Provenance filter — impa (in the IMPA Marine Stores Guide) or nermo (NERMO-authored, any domain). Composes with costCategory.","in":"query","name":"source","schema":{"enum":["impa","nermo"],"type":"string"}},{"description":"Domain filter — the product's shelf (provisions, technical-stores, …), resolved from the domain-rooted category tree. Independent of source.","in":"query","name":"costCategory","schema":{"$ref":"#/components/schemas/CostCategory"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CanonicalProductList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Browse/search the canonical product master list (client-readable).","tags":["products"]}},"/canonical-products/match":{"post":{"description":"Per line the serving cascade runs exact name/synonym → the caller's approved tenant mappings → semantic candidates (raw-cosine confidence, fused rank, attribute-veto filtered, the caller's rejections excluded). Only the exact tiers are auto-linkable; semantic results are suggestions requiring an explicit pick — confidence is a ranking hint, never truth, and no server-side threshold exists. Unmatched text stays valid and never blocks. degraded=true when the semantic tier could not serve (embed channel down or the tenant's daily embed budget exhausted); exact/tenant tiers still did. Requires products.catalog.view.","operationId":"MatchCanonicalProducts","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchCanonicalProductsRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchCanonicalProductsResult"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Bulk free-text → canonical-product resolution (read-class POST — the batch is a body, not a mutation; no Idempotency-Key).","tags":["products"]}},"/canonical-products/{productId}/substitutes":{"get":{"description":"Active substitutes only (a deprecated substitute is filtered at read — edges may lag a status flip), ordered by the deterministic (rank, name, id). Each row carries the edge's scope and the substitute's inherent allergens + dietaryTags so constraint-holding consumers filter client-side or via the shared eligibility helper. Advisory everywhere — nothing auto-substitutes, and vendors are never gated against the graph. No row → empty list (the model degrades gracefully). Requires products.catalog.view.","operationId":"ListCanonicalProductSubstitutes","parameters":[{"in":"path","name":"productId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"description":"The caller's context, not a scope filter — context=culinary returns only edges applicable when cooking (scope general; procurement-scoped hidden). Omitted returns all edges. The galley/recipe alternate picker passes it; procurement surfaces omit it.","in":"query","name":"context","schema":{"enum":["culinary"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CanonicalProductSubstituteList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"List a canonical product's T55 substitutes (party-neutral, advisory).","tags":["products"]}},"/canonical-products/{productId}/unit-suggestions":{"get":{"description":"The units used by the product's category kinship (the subtree under its category's parent), ranked by how many canonicals use each — mined from the master, not a curated per-product list. Purely an ordering/relevance hint for unit pickers: every unit code remains selectable, and a product with no category (or a master with no units in its kinship) yields an empty list. Requires products.catalog.view.","operationId":"ListCanonicalProductUnitSuggestions","parameters":[{"in":"path","name":"productId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnitSuggestionList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"List the units a canonical product plausibly trades in.","tags":["products"]}},"/catering-report":{"get":{"operationId":"GetCateringReport","parameters":[{"in":"query","name":"vesselId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"description":"How many months back, the current month included.","in":"query","name":"months","schema":{"default":12,"maximum":24,"minimum":1,"type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CateringReport"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"One vessel's realised manday per month over the last `months` months (the current month counted on its elapsed days): confirmed deliveries' line values — qty received × the order line's unit price, converted to USD at the ECB publication of the delivery date — ÷ the budget's crew count ÷ days, against the vessel budget's manday rate in USD, by product group. Requires fleet.benchmarking.view; a vessel outside the caller's scope is 404.","tags":["fleet"]}},"/company":{"get":{"operationId":"GetCompanyProfile","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyProfile"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"The tenant's own company profile. Requires admin.company.view.","tags":["identity"]},"patch":{"operationId":"UpdateCompanyProfile","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCompanyProfile"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyProfile"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Edit the Class-P (presentation) fields. Requires admin.company.manage. Identity fields are not accepted (additionalProperties:false); a key belonging to the other party is a 400.","tags":["identity"]}},"/company/agreement":{"get":{"operationId":"GetClientAgreementText","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientAgreementText"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"The published text of the Client Agreement version the platform presents — what a person reads before accepting it. Requires admin.company.view; client party only (404 for a vendor). The text is Markdown and never changes for a version; textSha256 is the SHA-256 of its UTF-8 bytes, the value an acceptance must carry.","tags":["identity"]}},"/company/agreement-acceptance":{"post":{"operationId":"AcceptClientAgreement","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptClientAgreement"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientAgreementAcceptance"}}},"description":"recorded"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Record the tenant's acceptance of the NERMO Client Agreement at `version`, resting on the exact text read (`textSha256`). The first vessel add requires it. Requires admin.company.manage; client party only (404 for a vendor). PORTAL ONLY: a person accepts the Agreement in the Client Portal, so a request whose token was not issued to the Client Portal's application — an MCP connector, a service account, any other API client — is refused with 403 portal-acceptance-required before anything is recorded. Idempotent on (tenant, version): re-accepting the recorded version returns the existing record (201 with the original acceptedAt); a version other than agreement.currentVersion → 409 client-agreement-version-mismatch; a textSha256 that is not the hash of that version's published text → 409 client-agreement-text-mismatch.","tags":["identity"]}},"/company/directors":{"get":{"operationId":"ListCompanyDirectors","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyDirectorList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"The tenant's declared beneficial owners / directors / signatories — names only, decrypted on read and AUDITED (one action='accessed' row per party). Requires admin.company.view.","tags":["identity"]}},"/compliance/sanctions/screen":{"post":{"operationId":"ScreenTenantSanctions","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreenTenantRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreeningRunResult"}}},"description":"recorded"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Screen a tenant now (staff, compliance.sanctions.manage) — the provider seam, or a staff disposition for tenants outside the onboarding flow. The screening leg behind public-listing publication.","tags":["compliance"]}},"/counterparty-flags":{"get":{"operationId":"ListCounterpartyFlags","parameters":[{"in":"query","name":"status","schema":{"enum":["active","withdrawn"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CounterpartyFlag"},"type":"array"}}},"description":"flags with incident counts"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"The vendor's own counterparty flags (projection — never carries client identity).","tags":["reputation"]},"post":{"operationId":"CreateCounterpartyFlag","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterpartyFlagInput"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterpartyFlag"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Flag the counterparty behind a dispatch or order. Duplicate active flag on the vessel → 409 counterparty-flag-conflict.","tags":["reputation"]}},"/counterparty-flags/{flagId}":{"get":{"operationId":"GetCounterpartyFlag","parameters":[{"in":"path","name":"flagId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterpartyFlagDetail"}}},"description":"flag + incident log"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Flag detail including the incident log.","tags":["reputation"]},"patch":{"operationId":"UpdateCounterpartyFlag","parameters":[{"in":"path","name":"flagId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterpartyFlagUpdate"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterpartyFlag"}}},"description":"updated"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Update the vendor's stance. status withdrawn = un-flag; withdrawn→active refused (create a fresh flag).","tags":["reputation"]}},"/counterparty-flags/{flagId}/incidents":{"post":{"operationId":"AddCounterpartyFlagIncident","parameters":[{"in":"path","name":"flagId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterpartyFlagIncidentInput"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterpartyFlagIncident"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Append a dated incident note under a flag (no dedup — every report appends).","tags":["reputation"]}},"/deliveries":{"get":{"operationId":"ListDeliveries","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Match against the document references (case-insensitive substring).","in":"query","name":"search","schema":{"maxLength":64,"type":"string"}},{"$ref":"#/components/parameters/DateFromParam"},{"$ref":"#/components/parameters/DateToParam"},{"in":"query","name":"status","schema":{"enum":["scheduled","in-transit","delivered","exception"],"type":"string"}},{"in":"query","name":"vesselId","schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"query","name":"orderId","schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeliveryList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List deliveries in scope.","tags":["procurement"]}},"/deliveries/{deliveryId}":{"get":{"operationId":"GetDelivery","parameters":[{"in":"path","name":"deliveryId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Delivery"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Delivery detail including receiving lines.","tags":["procurement"]}},"/deliveries/{deliveryId}/confirm":{"post":{"operationId":"ConfirmDelivery","parameters":[{"in":"path","name":"deliveryId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmDelivery"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Delivery"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Confirm receipt against a delivery (captain). Records POD + per-line received qty/status.","tags":["procurement"]}},"/deliveries/{deliveryId}/feedback":{"post":{"operationId":"SubmitVendorFeedback","parameters":[{"in":"path","name":"deliveryId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorFeedbackInput"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorFeedback"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Submit post-delivery vendor feedback (confirmed deliveries only — delivered|exception). One submission per user per delivery (duplicate → 409 vendor-feedback-conflict); unconfirmed → 409 delivery-status-conflict. NERMO-internal — never shown to the vendor.","tags":["reputation"]}},"/deliveries/{deliveryId}/report-delivered":{"post":{"operationId":"ReportDeliveryDelivered","parameters":[{"in":"path","name":"deliveryId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportDeliveryDelivered"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Delivery"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Vendor reports the delivery complete (+POD ref) and starts the client confirmation window. Already reported → 409 delivery-already-reported; concluded → 409 delivery-status-conflict.","tags":["procurement"]}},"/deliveries/{deliveryId}/stock-receipt":{"get":{"operationId":"GetDeliveryStockReceipt","parameters":[{"in":"path","name":"deliveryId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockReceipt"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"The delivery's stock receipt (404 until posted).","tags":["galley"]},"post":{"operationId":"PostDeliveryStockReceipt","parameters":[{"in":"path","name":"deliveryId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockReceipt"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Add the confirmed delivery's received quantities to the vessel's stock on board — once per delivery (a second post → 409 stock-receipt-exists). The delivery must have been confirmed (deliveredAt set; whatever its status) → 409 delivery-status-conflict otherwise. Lines are skipped, not rejected, with a reason: no-product (free text), unit-mismatch (a different dimension from the stock row), counted-after-delivery (the product's last count is on or after the delivery's physical date).","tags":["galley"]}},"/deliveries/{deliveryId}/transit":{"post":{"operationId":"MarkDeliveryInTransit","parameters":[{"in":"path","name":"deliveryId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Delivery"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Vendor marks the delivery dispatched (scheduled → in-transit). Informational — never a gate. Disallowed transition → 409 delivery-status-conflict.","tags":["procurement"]}},"/finance/invoices":{"get":{"operationId":"ListInvoices","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"in":"query","name":"status","schema":{"enum":["due","partially-paid","paid","disputed","void"],"type":"string"}},{"description":"Filter on the derived overdue predicate.","in":"query","name":"overdue","schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoiceList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"The tenant's own NERMO invoices (client → subscription, vendor → rebate; RLS-scoped, invoiceType discriminates). Staff read via /nermo/finance/invoices, never here.","tags":["finance"]}},"/finance/invoices/{invoiceId}":{"get":{"operationId":"GetInvoice","parameters":[{"in":"path","name":"invoiceId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"One invoice with lines + payment allocations embedded.","tags":["finance"]}},"/finance/order-payments":{"get":{"operationId":"ListOrderPayments","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"in":"query","name":"orderId","schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderPaymentList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Recorded goods-payment events, two-party (client payments.settlement.view / vendor payments.collections.view).","tags":["finance"]}},"/finance/rebate-rate":{"get":{"operationId":"GetRebateRate","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RebateRateInfo"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"The vendor's rebate rate standing today — the currently-effective agreement step (finance.rebate.view). 404 before the first agreement step.","tags":["finance"]}},"/finance/rebates":{"get":{"operationId":"ListVendorRebates","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"in":"query","name":"status","schema":{"enum":["accrued","invoiced","settled","waived"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorRebateList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Vendor rebate statement — per-order accruals + the tenant's USD running totals (finance.rebate.view).","tags":["finance"]}},"/finance/receivables":{"get":{"operationId":"ListReceivables","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"in":"query","name":"outstandingOnly","schema":{"default":true,"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReceivableList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Vendor goods-receivables aging projection over delivered orders ⋈ order_payments (payments.collections.view).","tags":["finance"]}},"/finance/subscription":{"get":{"operationId":"GetSubscription","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubscriptionInfo"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"The client's active subscription rate + currency + current period anchor (feeds the F-4 add-vessel consent line). 404 for spot tenants.","tags":["finance"]}},"/inbox":{"get":{"operationId":"ListVendorJourneys","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Match against the RFQ reference and, once awarded, the order reference (case-insensitive substring).","in":"query","name":"search","schema":{"maxLength":64,"type":"string"}},{"in":"query","name":"journeyStage","schema":{"enum":["new","viewed","quoted","pending-acceptance","won","in-transit","delivered","exception","invoiced","lapsed"],"type":"string"}},{"description":"Filter on the exception flag carried beside the stage (true = the delivery is flagged or a line came in short/subst/damaged, whatever the stage reads).","in":"query","name":"hasException","schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProcurementJourneyList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List procurement journeys for the calling vendor (dispatch-rooted, unified lifecycle).","tags":["procurement"]}},"/me":{"get":{"operationId":"GetMe","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActorContext"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/InternalError"}},"summary":"Current actor's authorization context.","tags":["identity"]}},"/me/connections":{"get":{"operationId":"ListMyConnections","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpConnectionList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/InternalError"}},"summary":"The calling user's live AI-application connections (hosted-connector grants approved on the NERMO connect page). Self-scoped like /me — no permission key; user principals only. Revoked connections are not listed (history is the audit log).","tags":["identity"]}},"/me/connections/{connectionId}":{"delete":{"operationId":"RevokeMyConnection","parameters":[{"in":"path","name":"connectionId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"revoked"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/RevokeUnavailable"}},"summary":"Revoke one of the calling user's AI-application connections: the grant's Zitadel session is terminated first (the application can no longer refresh its token and must be reconnected), then the connection is stamped revoked. Other connections of the user are untouched; an already-issued access token lives out its short TTL. Self-scoped like /me. An unknown or already-revoked connection is a 404; a refused session termination is a 503 revoke-unavailable and the connection stays live.","tags":["identity"]}},"/me/notification-preferences":{"get":{"operationId":"ListMyNotificationPreferences","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreferenceList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/InternalError"}},"summary":"The calling user's preference-controllable notification templates with their effective values (explicit row if one exists, else the template's default). Self-scoped like /me — no permission key; user principals only.","tags":["identity"]}},"/me/notification-preferences/{templateId}":{"put":{"operationId":"SetMyNotificationPreference","parameters":[{"in":"path","name":"templateId","required":true,"schema":{"maxLength":100,"minLength":1,"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetNotificationPreference"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NotificationPreference"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Set the calling user's preference for one template — a declarative upsert of an explicit per-user row (never a delete; writing the default value back stores a row). Self-scoped like /me. A templateId outside the caller's preference-controllable set is a 404.","tags":["identity"]}},"/me/sessions":{"get":{"operationId":"ListMySessions","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignedInSessionList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/SessionsUnavailable"}},"summary":"The calling user's live sessions: the browsers signed in to NERMO and the sessions behind their connected AI applications, read live from the identity provider. Self-scoped like /me — no permission key; user principals only. A 503 revoke-unavailable means the identity provider could not be read.","tags":["identity"]}},"/me/sessions/{sessionId}":{"delete":{"operationId":"RevokeMySession","parameters":[{"in":"path","name":"sessionId","required":true,"schema":{"maxLength":64,"minLength":1,"type":"string"}}],"responses":{"204":{"description":"ended"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/RevokeUnavailable"}},"summary":"End one of the calling user's sessions at the identity provider: a browser is signed out (its next token refresh fails; an already-issued access token lives out its short TTL); a session behind a connected AI application revokes that connection (the same Zitadel-first order as the connections revoke). Self-scoped like /me. A session that is not the caller's or is already gone is a 404; a refused termination is a 503 revoke-unavailable and the session stays live.","tags":["identity"]}},"/menu-plans":{"get":{"operationId":"ListMenuPlans","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"true → templates only; false → non-templates only.","in":"query","name":"template","schema":{"type":"boolean"}},{"in":"query","name":"status","schema":{"enum":["draft","confirmed","rfq-generated","archived"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuPlanList"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Menu plans in scope. Lists default to unarchived when status is omitted; vessel-bound plans follow the assigned-vessels scope (vessel-less plans + templates are tenant-wide).","tags":["galley"]},"post":{"operationId":"CreateMenuPlan","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMenuPlan"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuPlan"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Create a menu plan (a draft). Optional fromMenuPlanId copies the source's slots + crew_complement + duration_days (point-in-time); the daily allowances come from the vessel's standing list when the plan has a vessel (the source's only when the vessel has none), else from the source; isTemplate=true drops vessel/dates.","tags":["galley"]}},"/menu-plans/{menuPlanId}":{"get":{"operationId":"GetMenuPlan","parameters":[{"in":"path","name":"menuPlanId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuPlan"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Plan detail with the slot grid (live recipe name/status/serves) + generated-RFQ refs embedded.","tags":["galley"]},"patch":{"operationId":"UpdateMenuPlan","parameters":[{"in":"path","name":"menuPlanId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMenuPlan"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuPlan"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Edit a draft plan (attrs + batch slot replace-set; the grid saves atomically). Draft-only → 409 menu-plan-status-conflict; only published recipes are slottable.","tags":["galley"]}},"/menu-plans/{menuPlanId}/allowances":{"put":{"operationId":"SetMenuPlanAllowances","parameters":[{"in":"path","name":"menuPlanId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetAllowances"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuPlan"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Replace a draft plan's allowance list (full replace; the list rides getMenuPlan as allowances[]). Draft-only → 409 menu-plan-status-conflict.","tags":["galley"]}},"/menu-plans/{menuPlanId}/archive":{"post":{"operationId":"ArchiveMenuPlan","parameters":[{"in":"path","name":"menuPlanId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuPlan"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Archive a plan (any non-archived status; the only exit from the working list — nothing deletes). Already-archived → 409 menu-plan-status-conflict.","tags":["galley"]}},"/menu-plans/{menuPlanId}/confirm":{"post":{"operationId":"ConfirmMenuPlan","parameters":[{"in":"path","name":"menuPlanId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuPlan"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Confirm a draft plan (grid validation — ≥1 slot, durationDays required, every day_index \u003c duration_days). Freezes slot assignments, not recipe contents. Draft-only → 409 menu-plan-status-conflict.","tags":["galley"]}},"/menu-plans/{menuPlanId}/generate-rfq":{"post":{"operationId":"GenerateRfqFromMenuPlan","parameters":[{"in":"path","name":"menuPlanId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateRfqFromMenuPlan"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Rfq"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Generate an RFQ draft from a confirmed plan (scale by servings/serves → unit-convert within one dimension → aggregate, merging the plan's daily allowances → buffer → ceil to 3dp → net the vessel's stock on board (own product first, then eligible substitutes' stock; netStockOnBoard, default true) → order only the shortfall; covered lines are not RFQ lines). Every line's arithmetic is kept as the generation record (getRfq.generation). Plan must be confirmed or rfq-generated (re-generation creates another draft). Returns the created draft — the client reviews/edits/sends in the RFQ workbench.","tags":["galley"]}},"/menu-plans/{menuPlanId}/reopen":{"post":{"operationId":"ReopenMenuPlan","parameters":[{"in":"path","name":"menuPlanId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuPlan"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Reopen a plan to draft (from confirmed or rfq-generated; generated drafts are frozen copies — nothing shifts). Other states → 409 menu-plan-status-conflict.","tags":["galley"]}},"/nermo/audit-log":{"get":{"operationId":"AdminListAuditEvents","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"$ref":"#/components/parameters/DateFromParam"},{"$ref":"#/components/parameters/DateToParam"},{"description":"Restrict to a single tenant — a plain tenant_id filter, so a vendor tenant id scopes the same way (the param name predates the vendor audit surface).","in":"query","name":"clientId","schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditEventList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Cross-tenant audit event log. Optional ?clientId= filter.","tags":["admin"]}},"/nermo/canonical-products":{"get":{"description":"The staff view of the canonical master: q/categoryId/source behave exactly like the shared GET /canonical-products; status defaults to active and additionally admits deprecated | all (deprecated rows are invisible on the shared op by design). embeddingModel is null for a created-but-unembedded row; the vector itself never serializes.\n","operationId":"AdminListCanonicalProducts","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Text search over product name, IMPA code, or an approved synonym (shared-op semantics).","in":"query","name":"q","schema":{"maxLength":200,"type":"string"}},{"in":"query","name":"categoryId","schema":{"$ref":"#/components/schemas/Uuid"}},{"description":"Provenance filter — impa (guide) or nermo (NERMO-authored, any domain).","in":"query","name":"source","schema":{"enum":["impa","nermo"],"type":"string"}},{"description":"IMPA-code filter — true keeps rows carrying an impa_code, false keeps uncoded rows. Distinct from source: the guide's 00-section provisions rows carry a code but are source=nermo (one provisions master, no dual listing), so \"in the IMPA guide\" is this filter, not source=impa. Composes with source and costCategory.","in":"query","name":"impaCoded","schema":{"type":"boolean"}},{"description":"Domain filter — resolved from the domain-rooted category tree; composes with source.","in":"query","name":"costCategory","schema":{"$ref":"#/components/schemas/CostCategory"}},{"in":"query","name":"status","schema":{"default":"active","enum":["active","deprecated","all"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCanonicalProductList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Staff master list — shared shape + status/embeddingModel/synonymCount. Requires nermo.matching.manage.","tags":["matching"]},"post":{"description":"Deliberately minimal and NERMO-authored-only (source is server-fixed; IMPA governance stays seed/ops). Idempotency-keyed — T22 has no name-unique backstop, so an unkeyed retry would duplicate the master this surface exists to keep clean.\n","operationId":"CreateCanonicalProduct","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCanonicalProduct"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CanonicalProduct"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Create a NERMO-authored canonical product from curation (D2).","tags":["matching"]}},"/nermo/canonical-products/{productId}":{"get":{"operationId":"AdminGetCanonicalProduct","parameters":[{"in":"path","name":"productId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCanonicalProductDetail"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Staff canonical-product detail — row + categoryPath + evidence counts. Requires nermo.matching.manage.","tags":["matching"]}},"/nermo/canonical-products/{productId}/deprecate":{"post":{"description":"Plain deprecate flips status. With mergeIntoCanonicalProductId (400 on self-merge; target must be active + NERMO-authored): ALL of the source's synonyms move to the target, keepNameAsSynonym optionally adds the source's name-norm as a synonym on the target (through the norm gates, excluding the source row itself), and the source deprecates — one transaction. The same transaction clears the pending aftermath the engine's audit never touches: pending T62 rows proposing the source lose their proposal (they re-propose next run) and pending engine T23 rows targeting the source are deleted (approved/manual rows are human verdicts and stay, as does promoted_ref history — no forwarding column exists, by design). The T23 cleanup is a cross-tenant write: when foreign pending proposals exist the actor additionally needs nermo.cross-tenant.write (403 + full rollback otherwise). Already-deprecated returns 409 canonical-status-conflict.\n","operationId":"DeprecateCanonicalProduct","parameters":[{"in":"path","name":"productId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeprecateCanonicalProduct"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeprecateCanonicalProductResult"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Deprecate a NERMO-authored canonical, optionally merging into an active NERMO-authored target. Requires nermo.catalog.manage.","tags":["matching"]}},"/nermo/canonical-products/{productId}/import-variants":{"get":{"description":"promoted_ref is joined both ways — canonical_product:\u003cid\u003e directly, and product_synonym:\u003cid\u003e via the synonym's owning canonical (the goldenset reconstruction).\n","operationId":"AdminListCanonicalProductImportVariants","parameters":[{"in":"path","name":"productId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportVariantEvidenceList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"T62 promoted rows resolved onto this canonical. Requires nermo.matching.manage.","tags":["matching"]}},"/nermo/canonical-products/{productId}/mappings":{"get":{"operationId":"AdminListCanonicalProductMappings","parameters":[{"in":"path","name":"productId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MappingEvidenceList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"T23 mappings targeting this canonical (tenant-attributed evidence). Requires nermo.matching.manage.","tags":["matching"]}},"/nermo/canonical-products/{productId}/observations":{"get":{"description":"v1 keeps the join simple — pending rows whose norm equals the canonical's name norm or one of its synonym norms, plus rows carrying a pending engine proposal onto it (same tenant + norm via the pending T23 engine row).\n","operationId":"AdminListCanonicalProductObservations","parameters":[{"in":"path","name":"productId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ObservationEvidenceList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"T25 pending observations for this canonical. Requires nermo.matching.manage.","tags":["matching"]}},"/nermo/canonical-products/{productId}/recategorize":{"post":{"description":"The target category must exist and be a leaf or a section — never a root (section-level products are a legitimate live shape; root-level are not). The product must be active — a deprecated row is frozen history and refuses with 409 canonical-status-conflict.\n","operationId":"RecategorizeCanonicalProduct","parameters":[{"in":"path","name":"productId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecategorizeCanonicalProduct"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CanonicalProduct"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Re-home a NERMO-authored canonical onto a leaf or section category. Requires nermo.catalog.manage.","tags":["matching"]}},"/nermo/canonical-products/{productId}/rename":{"post":{"description":"Norm gates (atomic): the new name's norm must not be owned by a DIFFERENT active canonical nor by a synonym pointing elsewhere — 409 synonym-norm-conflict (the target's own synonym is fine; a no-op rename returns the row unchanged). On success the embedding + embedding_model are NULLED — the engine's next embed run re-embeds (cosine against the dead name would be wrong). The old name is deliberately NOT auto-written as a synonym: previousName in the response lets the caller decide (the 2026-07-09 hygiene batch kept old names selectively, by human judgment). IMPA rows are refused (400) — NERMO-authored rows only in v1.\n","operationId":"RenameCanonicalProduct","parameters":[{"in":"path","name":"productId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameCanonicalProduct"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RenameCanonicalProductResult"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Rename a NERMO-authored canonical product. Requires nermo.catalog.manage.","tags":["matching"]}},"/nermo/canonical-products/{productId}/retag":{"post":{"description":"Both arrays are the complete new truth — an empty array clears. Values are the locked allergen (EU-14) and dietary_tag enums; out-of-vocabulary values are refused by contract validation. The product must be active — a deprecated row is frozen history and refuses with 409 canonical-status-conflict.\n","operationId":"RetagCanonicalProduct","parameters":[{"in":"path","name":"productId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RetagCanonicalProduct"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CanonicalProduct"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Set the inherent allergens + dietary tags on a NERMO-authored canonical (full replace). Requires nermo.catalog.manage.","tags":["matching"]}},"/nermo/canonical-products/{productId}/substitutes/{substituteId}":{"delete":{"description":"204 whether or not the edge existed (idempotent delete); 404 only when the source canonical product itself is unknown.\n","operationId":"DeleteProductSubstitute","parameters":[{"in":"path","name":"productId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"substituteId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"deleted (or was already absent)"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Delete a T55 substitute edge. Requires nermo.catalog.manage.","tags":["matching"]},"put":{"description":"Declarative on the (canonical_product_id, substitute_canonical_product_id) key — naturally idempotent, no Idempotency-Key. FULL master (SB-3 ruling): an edge references rows without mutating them, so the NERMO-authored fence on master mutation does not apply. Both participants must exist (404) and be active (409 canonical-status-conflict); a self-edge is a 400. Edges are DIRECTED — the reverse edge is a separate deliberate write, never inferred. Audited like every master mutation.\n","operationId":"UpsertProductSubstitute","parameters":[{"in":"path","name":"productId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"substituteId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertProductSubstitute"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CanonicalProductSubstitute"}}},"description":"ok — the edge as read back (substitute identity + tags + rank)"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Upsert a directed T55 substitute edge (full master). Requires nermo.catalog.manage.","tags":["matching"]}},"/nermo/canonical-products/{productId}/synonyms":{"get":{"operationId":"AdminListCanonicalProductSynonyms","parameters":[{"in":"path","name":"productId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductSynonymList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"T59 synonyms on a canonical, with provenance. Requires nermo.matching.manage.","tags":["matching"]}},"/nermo/clients":{"get":{"operationId":"AdminListClients","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List all client tenants (cross-tenant). Requires nermo.clients.manage.","tags":["admin"]},"post":{"operationId":"AdminCreateClient","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateClient"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientDetail"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Create a client tenant; seeds system roles for the tenant_type.","tags":["admin"]}},"/nermo/clients/{clientId}":{"get":{"operationId":"AdminGetClient","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientDetail"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Client tenant detail.","tags":["admin"]},"patch":{"operationId":"AdminUpdateClient","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateClient"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientDetail"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Update a client tenant (rename).","tags":["admin"]}},"/nermo/clients/{clientId}/agreement-acceptance":{"post":{"operationId":"AdminAcceptClientAgreement","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminAcceptClientAgreement"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientAgreementAcceptance"}}},"description":"recorded"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Record a client's acceptance of the NERMO Client Agreement at `version` on a WRITTEN INSTRUMENT — the staff door beside the portal reader, for a client that executes on paper. The recorded acceptance carries source=staff, the instrument `reference`, and the SHA-256 of the version's published text (the text the instrument executes). Requires nermo.clients.manage; the target must be a client tenant (404 otherwise). Idempotent on (tenant, version): re-recording the version already on record returns the existing acceptance unchanged; a version other than the current one → 409 client-agreement-version-mismatch.","tags":["admin"]}},"/nermo/clients/{clientId}/budgets":{"get":{"operationId":"AdminListClientBudgets","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VesselBudgetList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"List per-vessel budgets on a client tenant (with missing-budget count).","tags":["admin"]},"post":{"operationId":"AdminCreateClientBudget","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVesselBudget"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VesselBudget"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Set a vessel's budget on a client tenant (insert-only history, keyed by IMO).","tags":["admin"]}},"/nermo/clients/{clientId}/budgets/{budgetId}":{"delete":{"operationId":"AdminDeleteClientBudget","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"budgetId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"deleted"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Delete a vessel budget row.","tags":["admin"]}},"/nermo/clients/{clientId}/contract-end":{"post":{"operationId":"AdminEndClientContract","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EndClientContract"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientDetail"}}},"description":"contract ended"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"End the client contract (reason required; refused while any vessel is active or pending). Gates every billing scan; existing debt and dunning are untouched.","tags":["admin"]}},"/nermo/clients/{clientId}/contract-reopen":{"post":{"operationId":"AdminReopenClientContract","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReopenClientContract"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientDetail"}}},"description":"contract reopened"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Reopen an ended client contract (billing resumes once a fresh rate step is appended).","tags":["admin"]}},"/nermo/clients/{clientId}/deactivate":{"post":{"operationId":"AdminDeactivateClient","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientDetail"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Deactivate a client tenant.","tags":["admin"]}},"/nermo/clients/{clientId}/directors":{"get":{"operationId":"AdminListClientDirectors","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyDirectorList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"A client's KYC directors (names only), audited to the NERMO tenant. Requires nermo.clients.manage.","tags":["admin"]}},"/nermo/clients/{clientId}/invoices/generate":{"post":{"operationId":"AdminGenerateSubscriptionInvoice","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateSubscriptionInvoice"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}},"description":"issued"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Issue the period subscription invoice by hand (pilot escape hatch + backfill; 409 on an already-issued period).","tags":["admin"]}},"/nermo/clients/{clientId}/reactivate":{"post":{"operationId":"AdminReactivateClient","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientDetail"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Reactivate a client tenant.","tags":["admin"]}},"/nermo/clients/{clientId}/roles":{"get":{"operationId":"AdminListClientRoles","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoleList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"List roles on a client tenant.","tags":["admin"]},"post":{"operationId":"AdminCreateClientRole","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRole"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Role"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Create a custom role on a client tenant (role-assist). Keys are validated against the TARGET client's party; reserved/cross-party keys → 400.","tags":["admin"]}},"/nermo/clients/{clientId}/subscriptions":{"get":{"operationId":"AdminListClientSubscriptions","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientSubscriptionList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"A client's subscription rate steps (versioned, append-only).","tags":["admin"]},"post":{"operationId":"AdminCreateClientSubscription","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateClientSubscription"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientSubscription"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Append a rate step (currency must equal contract_currency; a same-effectiveFrom step supersedes — latest-created wins in the as-of lookups).","tags":["admin"]}},"/nermo/clients/{clientId}/users":{"get":{"operationId":"AdminListClientUsers","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"List users on a client tenant.","tags":["admin"]},"post":{"operationId":"AdminCreateClientUser","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserOnTenant"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedUserWithTempPassword"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Provision a user on a client tenant; returns a one-time temporary password.","tags":["admin"]}},"/nermo/clients/{clientId}/vessels":{"get":{"operationId":"AdminListClientVessels","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VesselList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"List vessels on a client tenant.","tags":["admin"]},"post":{"operationId":"AdminCreateClientVessel","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminCreateClientVessel"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Vessel"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Register a vessel on a client tenant. Requires nermo.clients.manage. The body carries the staff acceptance — the reference to the client's written instrument; the vessel is born pending and bills from its activation at the account's rate as of today.","tags":["admin"]}},"/nermo/clients/{clientId}/vessels/{vesselId}":{"patch":{"operationId":"AdminUpdateClientVessel","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"vesselId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminUpdateClientVessel"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Vessel"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Update a vessel's metadata on a client tenant (incl. in-place IMO correction). Requires nermo.clients.manage.","tags":["admin"]}},"/nermo/clients/{clientId}/vessels/{vesselId}/decommission":{"delete":{"operationId":"AdminCancelClientVesselDecommission","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"vesselId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Vessel"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Cancel a vessel's scheduled decommission on a client tenant. Requires nermo.clients.manage.","tags":["admin"]},"post":{"operationId":"AdminDecommissionClientVessel","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"vesselId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecommissionVessel"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Vessel"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Decommission a vessel on a client tenant (immediate or scheduled). Requires nermo.clients.manage.","tags":["admin"]}},"/nermo/clients/{clientId}/vessels/{vesselId}/reactivate":{"post":{"operationId":"AdminReactivateClientVessel","parameters":[{"in":"path","name":"clientId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"vesselId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminReactivateClientVessel"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Vessel"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Reactivate a decommissioned OR declined vessel on a client tenant — a fresh staff acceptance (the reference in the body); the vessel re-enters pending and takes the normal flip. Requires nermo.clients.manage.","tags":["admin"]}},"/nermo/compliance-documents":{"get":{"operationId":"AdminListComplianceDocuments","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Comma-separated statuses; omit for all.","explode":false,"in":"query","name":"status","schema":{"items":{"$ref":"#/components/schemas/ComplianceDocStatus"},"type":"array"},"style":"form"},{"in":"query","name":"docType","schema":{"$ref":"#/components/schemas/ComplianceDocType"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminComplianceDocumentList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Cross-vendor compliance-document queue (staff). Filter by statuses for the attention view (pending-review / expiring / expired). Requires nermo.vendors.manage.","tags":["compliance","admin"]}},"/nermo/compliance/sanctions/reviews":{"get":{"operationId":"AdminListScreeningReviews","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreeningReviewList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"The Review lane (staff, compliance.sanctions.manage): tenants that are activated and still have at least one screened entity awaiting a staff disposition (latest result manual-review) — client and vendor, with or without an onboarding case. Oldest pending first.","tags":["compliance","admin"]}},"/nermo/compliance/sanctions/screenings":{"get":{"operationId":"AdminListTenantScreening","parameters":[{"in":"query","name":"tenantId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantScreeningTrail"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"A tenant's screening trail keyed on the tenant (staff, compliance.sanctions.manage) — the case detail's shape, available for case-less tenants too. Kyc-party name disclosure writes the directors-read audit row per party.","tags":["compliance","admin"]}},"/nermo/finance/invoices":{"get":{"operationId":"AdminListInvoices","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"in":"query","name":"tenantId","schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"query","name":"status","schema":{"enum":["due","partially-paid","paid","disputed","void"],"type":"string"}},{"in":"query","name":"invoiceType","schema":{"enum":["subscription","vendor-rebate"],"type":"string"}},{"description":"Filter on the derived overdue predicate.","in":"query","name":"overdue","schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InvoiceList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Staff cross-tenant invoice list (filters tenantId/status/invoiceType/overdue) — the v1 dunning list + the detail-tab invoice lists.","tags":["admin"]}},"/nermo/insights/covered-ports":{"get":{"operationId":"AdminListCoveredPorts","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CoveredPorts"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Every port with at least one active vendor covering it — product/basket-agnostic, so it carries no vendor name, price or count and rides nermo.insights.manday.view alone. Lets the manday simulator's own map show \"has NERMO supply\" for a port before it is added to the trading area, the way the price map already can off its own per-product coverage.","tags":["admin"]}},"/nermo/insights/manday-estimate":{"post":{"operationId":"AdminEstimateManday","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MandayEstimateRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MandayEstimate"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"The manday cost estimate — crews (headcount + the weekly recipe servings of their rotation + extra per-crew-day lines) priced at the trading-area ports (weighted by calls per year) at the cheapest active vendor offer per product, wastage on food lines only, water without wastage: per-port manday, the calls-weighted blend, the cost by product group, and the regional-estimate flag where a port is below the vendor floor or has no NERMO supply. Read-class (nothing is persisted, no idempotency key); a POST because the input is a document. With prospectFacing=false the founder view adds the median-priced figure, vendor counts and the vendor carrying most of each port's basket. Requires nermo.insights.manday.view.","tags":["admin"]}},"/nermo/insights/product-prices":{"get":{"operationId":"AdminListProductPrices","parameters":[{"description":"The product to price at every covered port, per its standard unit.","in":"query","name":"canonicalProductId","schema":{"$ref":"#/components/schemas/Uuid"}},{"description":"A shared-corpus menu template whose rotation (the recipe lines plus the template's per-person-per-day allowances, no wastage; per-vessel-per-day allowances stay out because the basket is per crew member) is priced as one crew member's daily basket at every covered port. A template that carries allowances must have a durationDays, else 400.","in":"query","name":"menuPlanTemplateId","schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductPriceMap"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"The price map — one canonical product (canonicalProductId) OR one crew basket (menuPlanTemplateId, a shared-corpus menu template) priced at every port with an active vendor carrying it: per port the cheapest and the median vendor offer in USD, the vendor count, the age of the price that fed each figure, and the cheapest vendor's name and quoted local price. Exactly one of the two selectors. Requires nermo.insights.prices.view (internal — vendor names and line prices in the payload).","tags":["admin"]}},"/nermo/invoices/{invoiceId}":{"get":{"operationId":"AdminGetInvoice","parameters":[{"in":"path","name":"invoiceId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Staff invoice detail — any tenant's invoice with lines + payment allocations embedded (sources the paymentId for a reversal).","tags":["admin"]}},"/nermo/invoices/{invoiceId}/credits":{"post":{"operationId":"AdminIssueInvoiceCredit","parameters":[{"in":"path","name":"invoiceId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueInvoiceCredit"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditNote"}}},"description":"credit note issued"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Issue a goodwill credit note against a subscription invoice (reason required; capped at the invoice total; applies up to the outstanding balance, the remainder carries forward).","tags":["admin"]}},"/nermo/invoices/{invoiceId}/payments":{"post":{"operationId":"AdminRecordInvoicePayment","parameters":[{"in":"path","name":"invoiceId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordInvoicePayment"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}},"description":"recorded"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Record a receipt against an invoice (payment + allocation; invoice status re-derived in the same tx; receipt currency server-set from the invoice).","tags":["admin"]}},"/nermo/invoices/{invoiceId}/void":{"post":{"operationId":"AdminVoidInvoice","parameters":[{"in":"path","name":"invoiceId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoidInvoice"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}},"description":"voided"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Void an invoice (reason required; terminal; refused with a settled receipt). The freed unique slot lets the sweep reissue the period/stub; a rebate invoice's rebates revert to accrued.","tags":["admin"]}},"/nermo/matching/import-candidates":{"get":{"operationId":"ListImportCandidates","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"in":"query","name":"kind","schema":{"enum":["canonical","variant"],"type":"string"}},{"in":"query","name":"status","schema":{"enum":["pending","promoted","discarded"],"type":"string"}},{"in":"query","name":"batchRef","schema":{"type":"string"}},{"description":"Exact family filter — all variant rows mined from one source canonical.","in":"query","name":"minedFromCanonical","schema":{"type":"string"}},{"description":"variant-count sorts busiest spellings first; confidence sorts by the engine proposal (descending; unproposed rows last).","in":"query","name":"sort","schema":{"default":"variant-count","enum":["variant-count","confidence"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportCandidateList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List T62 import candidates (family view via minedFromCanonical). Requires nermo.matching.manage.","tags":["matching"]}},"/nermo/matching/import-candidates/{candidateId}/resolve":{"post":{"description":"Single-row, keyed. promote-synonym pre-checks synonym_norm uniqueness — a norm already targeting a DIFFERENT canonical returns 409 and the row stays pending (that collision is the ambiguity signal). create-canonical runs the D2 create in the same transaction — on a head it re-points the pending family; on a variant it promotes the row's own text as a synonym onto the created canonical (the promote-synonym gates apply, and a gate refusal rolls the create back with it). map-to-existing re-points the family the same way (no kind flip). discard is terminal in the UI; rows are never deleted (no DELETE grant exists).\n","operationId":"ResolveImportCandidate","parameters":[{"in":"path","name":"candidateId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolveImportCandidate"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ImportCandidate"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Resolve one import candidate — promote-synonym | create-canonical | map-to-existing | discard.","tags":["matching"]}},"/nermo/matching/proposals":{"get":{"operationId":"ListMappingProposals","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"in":"query","name":"tenantId","schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"query","name":"minConfidence","schema":{"maximum":1,"minimum":-1,"type":"number"}},{"in":"query","name":"maxConfidence","schema":{"maximum":1,"minimum":-1,"type":"number"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MappingProposalList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List engine mapping proposals (T23 pending, match_source=engine). Requires nermo.matching.manage.","tags":["matching"]}},"/nermo/matching/proposals/{mappingId}/review":{"post":{"description":"CROSS-TENANT write (T23/T24 rows belong to client/vendor tenants): requires BOTH nermo.matching.manage and nermo.cross-tenant.write. approve flips review_state; reject writes the T24 negative-memory row and deletes the T23 pending row in one transaction (frees the identity key for a different future candidate; T24 blocks the same pair returning). retarget approves onto a caller-chosen canonical (canonicalProductId required): the engine's pair gets the T24 negative memory and the row becomes match_source=manual with confidence cleared, in one transaction. Only pending, engine-sourced rows are reviewable (409 otherwise).\n","operationId":"ReviewMappingProposal","parameters":[{"in":"path","name":"mappingId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReviewMappingProposal"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MappingReviewResult"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Review one engine proposal — approve | reject | retarget.","tags":["matching"]}},"/nermo/matching/unit-aliases/decide":{"post":{"operationId":"DecideUnitAlias","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DecideUnitAlias"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnitAliasVerdict"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Decide a unit notation — approve (requires a live unitCode) | stoplist.","tags":["matching"]}},"/nermo/matching/unit-observations":{"get":{"operationId":"ListUnitObservations","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnitObservationQueueList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List the undecided unit-notation queue (T60 vote groups; conflict-frozen notations first). Requires nermo.matching.manage.","tags":["matching"]}},"/nermo/menu-templates":{"get":{"operationId":"ListMenuTemplates","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"in":"query","name":"crewProfile","schema":{"$ref":"#/components/schemas/CrewProfile"}},{"in":"query","name":"status","schema":{"enum":["draft","confirmed","archived"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuTemplateList"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List the shared-corpus menu templates (paged; optional crewProfile / status filters; unarchived by default). Requires nermo.recipes.manage.","tags":["nermo-recipes"]},"post":{"operationId":"CreateMenuTemplate","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateMenuTemplate"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuTemplate"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Create a shared-corpus menu template (a draft, tenant-less) with its slot grid; only published corpus recipes are slottable. Requires nermo.recipes.manage.","tags":["nermo-recipes"]}},"/nermo/menu-templates/{templateId}":{"get":{"operationId":"GetMenuTemplate","parameters":[{"in":"path","name":"templateId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuTemplate"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"One shared-corpus menu template with its slot grid (live recipe name / status / serves). A client-owned plan id is 404.","tags":["nermo-recipes"]},"patch":{"operationId":"UpdateMenuTemplate","parameters":[{"in":"path","name":"templateId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMenuTemplate"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuTemplate"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Edit a draft template — full replace of name / crewProfile / durationDays + the slot replace-set (the grid saves atomically). Draft-only → 409 menu-plan-status-conflict; only published corpus recipes are slottable.","tags":["nermo-recipes"]}},"/nermo/menu-templates/{templateId}/archive":{"post":{"operationId":"ArchiveMenuTemplate","parameters":[{"in":"path","name":"templateId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuTemplate"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Archive a template (any non-archived status; nothing deletes). Already archived → 409 menu-plan-status-conflict.","tags":["nermo-recipes"]}},"/nermo/menu-templates/{templateId}/confirm":{"post":{"operationId":"ConfirmMenuTemplate","parameters":[{"in":"path","name":"templateId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuTemplate"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"The founder's sign-off — draft → confirmed (≥1 slot; every dayIndex \u003c durationDays). One confirmed template per crew profile — another one on the same profile → 409 menu-template-profile-taken. Draft-only → 409 menu-plan-status-conflict.","tags":["nermo-recipes"]}},"/nermo/menu-templates/{templateId}/reopen":{"post":{"operationId":"ReopenMenuTemplate","parameters":[{"in":"path","name":"templateId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuTemplate"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Reopen a confirmed template to draft (it stops being the profile's signed-off rotation until confirmed again). Other states → 409 menu-plan-status-conflict.","tags":["nermo-recipes"]}},"/nermo/onboarding/cases":{"get":{"operationId":"AdminListOnboardingCases","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"in":"query","name":"caseType","schema":{"$ref":"#/components/schemas/OnboardingCaseType"}},{"description":"onboarding_status (client) or vendor_status (vendor) value; omit for all.","in":"query","name":"status","schema":{"type":"string"}},{"description":"true = only cases escalated and undecided (the action lane).","in":"query","name":"escalated","schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingCaseList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"The onboarding exception queue (staff) — client + vendor cases with type/status/escalated filters and a duplicate hint. Requires nermo.onboarding.view.","tags":["onboarding","admin"]}},"/nermo/onboarding/cases/{reference}":{"get":{"operationId":"AdminGetOnboardingCase","parameters":[{"in":"path","name":"reference","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OnboardingCase"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"One case's full detail — application snapshot, screening trail (incl. match details — staff-only), timeline, vendor checklist. Requires nermo.onboarding.view.","tags":["onboarding","admin"]}},"/nermo/onboarding/cases/{reference}/override":{"post":{"operationId":"AdminOverrideOnboardingCase","parameters":[{"in":"path","name":"reference","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OverrideCaseRequest"}}},"required":true},"responses":{"204":{"description":"applied"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Break-glass override — force-advance (out-of-band approval decision; the workflow's terminal re-read converges) or restart (reset the workflow trigger). Reason required; WORM-audited. Requires nermo.onboarding.manage.","tags":["onboarding","admin"]}},"/nermo/onboarding/cases/{reference}/resolve":{"post":{"operationId":"AdminResolveOnboardingEscalation","parameters":[{"in":"path","name":"reference","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResolveEscalationRequest"}}},"required":true},"responses":{"204":{"description":"resolved"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Resolve an escalated case — approve clears the escalation and the workflow poll advances; decline is terminal (workflow rejects). 409 when the case is not escalated. Requires nermo.onboarding.manage.","tags":["onboarding","admin"]}},"/nermo/onboarding/cases/{reference}/screening-dispositions":{"post":{"operationId":"AdminRecordScreeningDisposition","parameters":[{"in":"path","name":"reference","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreeningDispositionRequest"}}},"required":true},"responses":{"204":{"description":"recorded"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Record a staff screening disposition for one screened entity (an authoritative manual sanctions_screening_events row — recency wins). 404 when the entity is not one of the case's screening targets. Requires nermo.onboarding.manage.","tags":["onboarding","admin"]}},"/nermo/onboarding/cases/{reference}/verify-banking":{"post":{"operationId":"AdminVerifyVendorBanking","parameters":[{"in":"path","name":"reference","required":true,"schema":{"type":"string"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"verified"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Record the staff payee-name-match banking verification on a vendor case (the pre-activation gate; 409 onboarding-case-conflict once the vendor is activated). 404 on client cases. Requires nermo.onboarding.manage.","tags":["onboarding","admin"]}},"/nermo/orders":{"get":{"operationId":"AdminListOrders","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Match against the document references (case-insensitive substring).","in":"query","name":"search","schema":{"maxLength":64,"type":"string"}},{"description":"The owning CLIENT tenant. Filter the vendor side with vendorId.","in":"query","name":"tenantId","schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"query","name":"status","schema":{"enum":["awaiting-vendor-acceptance","confirmed","delivered","invoiced","cancelled","declined","retracted"],"type":"string"}},{"in":"query","name":"vendorId","schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminOrderList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List orders across all tenants (filters tenantId/status/vendorId/search). Requires nermo.orders.manage.","tags":["admin"]}},"/nermo/orders/{orderId}":{"get":{"operationId":"AdminGetOrder","parameters":[{"in":"path","name":"orderId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Any tenant's order with its lines. Returns the party Order projection unchanged — it already names both counterparties (clientTenantId + client, vendorTenantId + vendor), so the staff read needs no attribution wrapper. Requires nermo.orders.manage.","tags":["admin"]}},"/nermo/orders/{orderId}/documents":{"get":{"operationId":"AdminListOrderDocuments","parameters":[{"in":"path","name":"orderId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/OrderDocument"},"type":"array"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Any order's goods invoices / credit notes (the vendor's own documents) with the reconciliation flag against the declared figures. Gated on nermo.orders.manage — the parent /nermo/orders read key, not a dedicated document key.","tags":["admin"]}},"/nermo/payment-methods":{"get":{"operationId":"AdminListPaymentMethods","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Comma-separated statuses; omit for all.","explode":false,"in":"query","name":"status","schema":{"items":{"enum":["pending","verified","disabled"],"type":"string"},"type":"array"},"style":"form"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminVendorPaymentMethodList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Cross-vendor payment-method queue (staff). Filter by statuses for the attention view (pending). Requires nermo.vendors.manage.","tags":["onboarding","admin"]}},"/nermo/payments/{paymentId}/reverse":{"post":{"operationId":"AdminReverseInvoicePayment","parameters":[{"in":"path","name":"paymentId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReverseInvoicePayment"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}},"description":"reversed — returns the re-derived invoice (the first allocated invoice if several)"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Reverse a mis-recorded receipt (reason required; allocations drop out of the Σ; invoice status re-derived). Receipts are never edited or deleted.","tags":["admin"]}},"/nermo/product-categories":{"get":{"description":"productCount / deprecatedCount are DIRECT per-node counts (no recursive rollup — the UI rolls up, D-5).\n","operationId":"AdminListProductCategories","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminProductCategoryList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Staff category tree — full rows + direct product counts. Requires nermo.matching.manage.","tags":["matching"]},"post":{"description":"The 3-level cap is handler-enforced — a parentId that is itself a leaf (its parent has a parent) returns 400, since nothing in the schema stops level-4 nesting and v1 has no restructuring ops to repair one. Duplicate code returns 409 duplicate-category-code. No category rename/delete ops exist in v1.\n","operationId":"CreateProductCategory","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductCategory"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductCategory"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Create a category slot — a section (parent = a root) or a leaf (parent = a section). Requires nermo.catalog.manage.","tags":["matching"]}},"/nermo/product-synonyms/{synonymId}":{"delete":{"description":"Deleting a synonym affects future matching only — matchsweep retro-links already written stay (no cascade, by design). Audited like every master mutation.\n","operationId":"DeleteProductSynonym","parameters":[{"in":"path","name":"synonymId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"deleted"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Delete a T59 synonym. Requires nermo.catalog.manage.","tags":["matching"]}},"/nermo/product-synonyms/{synonymId}/move":{"post":{"description":"Norm-safe by construction (synonym norms are globally unique — moving one changes no norm). A move to the synonym's current owner is a no-op success.\n","operationId":"MoveProductSynonym","parameters":[{"in":"path","name":"synonymId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MoveProductSynonym"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductSynonym"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Re-point a T59 synonym onto another active NERMO-authored canonical. Requires nermo.catalog.manage.","tags":["matching"]}},"/nermo/prospects":{"get":{"operationId":"ListProspects","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"in":"query","name":"search","schema":{"maxLength":200,"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProspectList"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List prospects (paged; optional search over company / contact name). Requires nermo.prospects.manage.","tags":["admin"]},"post":{"operationId":"CreateProspect","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProspectInput"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Prospect"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Create a prospect. Keyed.","tags":["admin"]}},"/nermo/prospects/{prospectId}":{"get":{"operationId":"GetProspect","parameters":[{"in":"path","name":"prospectId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Prospect"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"One prospect.","tags":["admin"]},"patch":{"operationId":"UpdateProspect","parameters":[{"in":"path","name":"prospectId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProspectUpdate"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Prospect"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Replace a prospect's fields (full replace); convertedClientId names the client tenant it became, null clears it. Keyed.","tags":["admin"]}},"/nermo/prospects/{prospectId}/simulations":{"get":{"operationId":"ListProspectSimulations","parameters":[{"in":"path","name":"prospectId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProspectSimulationList"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"A prospect's saved simulations, newest first — summaries with the headline manday, without the documents.","tags":["admin"]},"post":{"operationId":"CreateProspectSimulation","parameters":[{"in":"path","name":"prospectId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProspectSimulationInput"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProspectSimulation"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Save a simulation on the prospect — a name, the simulator's inputs document, and the figures as computed (a manday estimate). Keyed.","tags":["admin"]}},"/nermo/prospects/{prospectId}/simulations/{simulationId}":{"delete":{"operationId":"DeleteProspectSimulation","parameters":[{"in":"path","name":"prospectId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"simulationId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"deleted"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Delete one saved simulation.","tags":["admin"]},"get":{"operationId":"GetProspectSimulation","parameters":[{"in":"path","name":"prospectId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"simulationId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProspectSimulation"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"One saved simulation — the inputs and the figures as saved.","tags":["admin"]}},"/nermo/rebates/{rebateId}/waive":{"post":{"operationId":"AdminWaiveVendorRebate","parameters":[{"in":"path","name":"rebateId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaiveVendorRebate"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorRebate"}}},"description":"waived"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Waive a still-accrued rebate (reason required; invoiced rebates go through the adjustment seam, never a flip).","tags":["admin"]}},"/nermo/recipes":{"get":{"operationId":"ListCorpusRecipes","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"in":"query","name":"status","schema":{"enum":["draft","published","archived"],"type":"string"}},{"description":"Name substring search.","in":"query","name":"q","schema":{"maxLength":200,"type":"string"}},{"in":"query","name":"category","schema":{"type":"string"}},{"in":"query","name":"dietaryTag","schema":{"enum":["halal","kosher","vegetarian","vegan","pescatarian","no-pork","no-beef"],"type":"string"}},{"in":"query","name":"excludeAllergen","schema":{"enum":["celery","crustaceans","eggs","fish","gluten","lupin","milk","molluscs","mustard","nuts","peanuts","sesame","soybeans","sulphites"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeList"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Corpus recipe library (tenant_id IS NULL only). Requires nermo.recipes.manage.","tags":["nermo-recipes"]},"post":{"operationId":"CreateCorpusRecipe","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRecipe"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recipe"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Author a corpus recipe (always a draft; tenant_id NULL). forkFromRecipeId is rejected (400) — corpus is authored, never forked. Unknown units reject 400.","tags":["nermo-recipes"]}},"/nermo/recipes/{recipeId}":{"get":{"operationId":"GetCorpusRecipe","parameters":[{"in":"path","name":"recipeId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recipe"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Corpus recipe detail with ingredients embedded (tenant_id IS NULL only; shared=true). Requires nermo.recipes.manage.","tags":["nermo-recipes"]},"patch":{"operationId":"UpdateCorpusRecipe","parameters":[{"in":"path","name":"recipeId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateCorpusRecipe"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recipe"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Edit a corpus recipe (attrs + ingredient replace-set; full replace, the updateRfq shape). Corpus rows only (→ 404 for a client-owned id); never status; archived rows → 409 recipe-status-conflict. Changing allergens/dietaryTags on a published row requires attestedAllergenReview=true (G-4).","tags":["nermo-recipes"]}},"/nermo/recipes/{recipeId}/archive":{"post":{"operationId":"ArchiveCorpusRecipe","parameters":[{"in":"path","name":"recipeId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recipe"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Archive a corpus recipe (draft/published→archived; a safety pull). Corpus rows only (→ 404 for a client-owned id); already-archived → 409 recipe-status-conflict.","tags":["nermo-recipes"]}},"/nermo/recipes/{recipeId}/publish":{"post":{"operationId":"PublishCorpusRecipe","parameters":[{"in":"path","name":"recipeId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishCorpusRecipe"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recipe"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Publish a draft corpus recipe (draft→published). Requires attestedAllergenReview=true (G-4). Draft-only → 409 recipe-status-conflict.","tags":["nermo-recipes"]}},"/nermo/reputation/bars":{"get":{"operationId":"NermoListCounterpartyBars","responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/CounterpartyBar"},"type":"array"}}},"description":"bars"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List counterparty bars (active + lifted).","tags":["reputation"]},"post":{"operationId":"NermoCreateCounterpartyBar","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterpartyBarInput"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterpartyBar"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Impose a counterparty bar. Vessel scope needs vesselImo or vesselId; company scope needs clientTenantId. New RFQ sends for the target refuse with 403 counterparty-barred.","tags":["reputation"]}},"/nermo/reputation/bars/{barId}/lift":{"post":{"operationId":"NermoLiftCounterpartyBar","parameters":[{"in":"path","name":"barId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterpartyBarLift"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CounterpartyBar"}}},"description":"lifted"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Lift an active bar (reason required). Lifting a non-active bar → 400.","tags":["reputation"]}},"/nermo/reputation/config":{"get":{"operationId":"NermoGetReputationConfig","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReputationConfig"}}},"description":"config"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"The reputation_config singleton — window + thresholds.","tags":["reputation"]},"patch":{"operationId":"NermoUpdateReputationConfig","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReputationConfigUpdate"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReputationConfig"}}},"description":"updated config"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Update window/threshold knobs (partial).","tags":["reputation"]}},"/nermo/reputation/counterparties":{"get":{"operationId":"NermoListReputationCounterparties","parameters":[{"in":"query","name":"overThreshold","schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ReputationCounterpartyRow"},"type":"array"}}},"description":"counterparty rows"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Counterparty watchlist lane — per-client flag/incident aggregates with hull drill-down.","tags":["reputation"]}},"/nermo/reputation/feedback":{"get":{"operationId":"NermoListVendorFeedback","parameters":[{"in":"query","name":"vendorTenantId","schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/StaffVendorFeedback"},"type":"array"}}},"description":"feedback rows with tenant identity (staff projection)"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Signal browse — client feedback across deliveries (behind a vendor lane row).","tags":["reputation"]}},"/nermo/reputation/flags":{"get":{"operationId":"NermoListCounterpartyFlags","parameters":[{"in":"query","name":"clientTenantId","schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/StaffCounterpartyFlag"},"type":"array"}}},"description":"flags with vendor + client identity (staff projection)"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Signal browse — flags across vendors (behind a counterparty lane row).","tags":["reputation"]}},"/nermo/reputation/vendors":{"get":{"operationId":"NermoListReputationVendors","parameters":[{"in":"query","name":"overThreshold","schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/ReputationVendorRow"},"type":"array"}}},"description":"vendor rows"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Vendor watchlist lane — per-vendor feedback + exception-line aggregates.","tags":["reputation"]}},"/nermo/rfqs":{"get":{"operationId":"AdminListRfqs","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Match against the document references (case-insensitive substring).","in":"query","name":"search","schema":{"maxLength":64,"type":"string"}},{"in":"query","name":"tenantId","schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"query","name":"status","schema":{"enum":["draft","sent","replies-received","awarded","closed","cancelled"],"type":"string"}},{"description":"The port's 5-character UN/LOCODE. Matched case-insensitively.","in":"query","name":"portUnlocode","schema":{"maxLength":5,"minLength":5,"type":"string"}},{"in":"query","name":"vesselId","schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminRfqList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List RFQs across all client tenants (filters tenantId/status/portUnlocode/vesselId/search). Requires nermo.orders.manage.","tags":["admin"]}},"/nermo/rfqs/{rfqId}":{"get":{"operationId":"AdminGetRfq","parameters":[{"in":"path","name":"rfqId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminRfqDetail"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Any tenant's RFQ with its lines and received-quote summaries, plus the owning tenant. Requires nermo.orders.manage.","tags":["admin"]}},"/nermo/users":{"get":{"operationId":"AdminListUsers","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Restrict to a single client tenant.","in":"query","name":"clientId","schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List users across all tenants. Optional ?clientId= filter.","tags":["admin"]}},"/nermo/users/{userId}":{"delete":{"operationId":"AdminDeleteUser","parameters":[{"in":"path","name":"userId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"deleted"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Deactivate / remove a user (cross-tenant).","tags":["admin"]},"get":{"operationId":"AdminGetUser","parameters":[{"in":"path","name":"userId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"User detail (cross-tenant).","tags":["admin"]},"patch":{"operationId":"AdminUpdateUser","parameters":[{"in":"path","name":"userId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUser"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Update a user (cross-tenant).","tags":["admin"]}},"/nermo/users/{userId}/connections":{"get":{"operationId":"AdminListUserConnections","parameters":[{"in":"path","name":"userId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpConnectionList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"One customer user's live AI-application connections (cross-tenant). A NERMO-staff user as the target is a 403. Requires nermo.users.manage.","tags":["admin"]}},"/nermo/users/{userId}/connections/{connectionId}":{"delete":{"operationId":"AdminRevokeUserConnection","parameters":[{"in":"path","name":"userId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"connectionId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"revoked"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/RevokeUnavailable"}},"summary":"Revoke one of a customer user's AI-application connections (cross-tenant): the grant's Zitadel session is terminated first, then the connection is stamped revoked. An unknown, mismatched, or already-revoked connection is a 404; a NERMO-staff user as the target is a 403; a refused session termination is a 503 revoke-unavailable and the connection stays live. Requires nermo.users.manage.","tags":["admin"]}},"/nermo/users/{userId}/reactivate":{"post":{"operationId":"AdminReactivateUser","parameters":[{"in":"path","name":"userId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Reactivate a deactivated user.","tags":["admin"]}},"/nermo/users/{userId}/resend-invite":{"post":{"operationId":"AdminResendUserInvite","parameters":[{"in":"path","name":"userId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"invite mail sent (idempotent replay also 204 — no re-send)"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Re-send the user's first-login invite mail (Zitadel-owned, NERMO-branded; the link lands on their portal's activate page and replaces any earlier invite code). Only for a user who has never set a password — an activated user gets 409 user-already-activated; use reset-password for them.","tags":["admin"]}},"/nermo/users/{userId}/reset-password":{"post":{"operationId":"AdminResetUserPassword","parameters":[{"in":"path","name":"userId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"204":{"description":"reset mail sent (idempotent replay also 204 — no re-send)"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Send the user a password-reset mail (Zitadel-owned, NERMO-branded) whose link lands on their portal's reset page. The current password keeps working until the reset completes. Replaces the retired temp-password relay.","tags":["admin"]}},"/nermo/users/{userId}/sessions":{"get":{"operationId":"AdminListUserSessions","parameters":[{"in":"path","name":"userId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignedInSessionList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"503":{"$ref":"#/components/responses/SessionsUnavailable"}},"summary":"One customer user's live sessions (cross-tenant). A NERMO-staff user as the target is a 403. Requires nermo.users.manage.","tags":["admin"]}},"/nermo/users/{userId}/sessions/{sessionId}":{"delete":{"operationId":"AdminRevokeUserSession","parameters":[{"in":"path","name":"userId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"maxLength":64,"minLength":1,"type":"string"}}],"responses":{"204":{"description":"ended"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/RevokeUnavailable"}},"summary":"End one of a customer user's sessions (cross-tenant): a browser is signed out, a session behind a connected AI application revokes that connection. A session that is not the user's or is already gone is a 404; a NERMO-staff user as the target is a 403; a refused termination is a 503 revoke-unavailable and the session stays live. Requires nermo.users.manage.","tags":["admin"]}},"/nermo/vendors":{"get":{"operationId":"AdminListVendors","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List all vendor tenants (cross-tenant). Requires nermo.vendors.manage.","tags":["admin"]},"post":{"operationId":"AdminCreateVendor","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVendor"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorDetail"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Create a vendor tenant; seeds the vendor default roles. Defaults status to active.","tags":["admin"]}},"/nermo/vendors/{vendorId}":{"get":{"operationId":"AdminGetVendor","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorDetail"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Vendor tenant detail.","tags":["admin"]},"patch":{"operationId":"AdminUpdateVendor","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateVendor"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorDetail"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Flat update of a vendor (profile + lifecycle status + trustedFastTrack; no guards).","tags":["admin"]}},"/nermo/vendors/{vendorId}/catalog-items":{"get":{"operationId":"AdminListVendorCatalogItems","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Text search over name / SKU.","in":"query","name":"search","schema":{"maxLength":200,"type":"string"}},{"description":"Filter by active flag; omit for all.","in":"query","name":"active","schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminVendorCatalogItemList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"List one vendor's catalog items (staff) — the vendor's projection plus the price age, with the vendor's currency and the whole-catalog completeness summary. Requires nermo.vendors.manage. A non-vendor / unknown id is 404.","tags":["vendor-catalog","admin"]}},"/nermo/vendors/{vendorId}/compliance-documents":{"get":{"operationId":"AdminListVendorComplianceDocuments","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Comma-separated statuses; omit for all.","explode":false,"in":"query","name":"status","schema":{"items":{"$ref":"#/components/schemas/ComplianceDocStatus"},"type":"array"},"style":"form"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminComplianceDocumentList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"List one vendor's compliance documents (staff). Requires nermo.vendors.manage.","tags":["compliance","admin"]}},"/nermo/vendors/{vendorId}/compliance-documents/{docId}/reject":{"post":{"operationId":"AdminRejectComplianceDocument","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"docId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RejectComplianceDocumentRequest"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminComplianceDocument"}}},"description":"rejected"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Reject a pending-review document with the reason (notes required). Requires nermo.compliance.manage.","tags":["compliance","admin"]}},"/nermo/vendors/{vendorId}/compliance-documents/{docId}/verify":{"post":{"operationId":"AdminVerifyComplianceDocument","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"docId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyComplianceDocumentRequest"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminComplianceDocument"}}},"description":"verified"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Verify a document (staff decision of record; sets verified_by/at). From pending-review, or from expiring/expired together with a corrected future expiresAt. Requires nermo.compliance.manage.","tags":["compliance","admin"]}},"/nermo/vendors/{vendorId}/coverage":{"get":{"operationId":"AdminListVendorCoverage","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorCoverageList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"List a vendor's port coverage.","tags":["admin"]}},"/nermo/vendors/{vendorId}/coverage/{portId}":{"delete":{"operationId":"AdminDeleteVendorCoverage","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"portId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"deleted"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Delete a vendor's coverage for a port.","tags":["admin"]},"put":{"operationId":"AdminUpsertVendorCoverage","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"portId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertVendorCoverage"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorCoverage"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Upsert a vendor's coverage for a port (declarative, on the (tenant, port) key).","tags":["admin"]}},"/nermo/vendors/{vendorId}/deactivate":{"post":{"operationId":"AdminDeactivateVendor","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorDetail"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Deactivate a vendor tenant (tenants.status — the login kill-switch).","tags":["admin"]}},"/nermo/vendors/{vendorId}/directors":{"get":{"operationId":"AdminListVendorDirectors","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompanyDirectorList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"A vendor's KYC directors (names only), audited to the NERMO tenant. Requires nermo.vendors.manage.","tags":["admin"]}},"/nermo/vendors/{vendorId}/payment-methods":{"get":{"operationId":"AdminListVendorPaymentMethods","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorPaymentMethodList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"List one vendor's payment methods (staff; last-4 + holder name + metadata). Requires nermo.vendors.manage.","tags":["onboarding","admin"]}},"/nermo/vendors/{vendorId}/payment-methods/{methodId}/verify":{"post":{"operationId":"AdminVerifyVendorPaymentMethod","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"methodId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorPaymentMethod"}}},"description":"verified"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Verify one pending payment method (staff payee-name check of record). Flips the vendor's onboarding banking gate only when no method is left pending. Not pending → 409 payment-method-status-conflict. Requires nermo.onboarding.manage.","tags":["onboarding","admin"]}},"/nermo/vendors/{vendorId}/publish":{"post":{"operationId":"AdminPublishVendor","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorDetail"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Staff override — publish the vendor's public page. Refuses (409 vendor-not-publishable) unless the vendor has recorded listing consent, is active, and the latest tenant sanctions screening is clear; mints directorySlug on first publish.","tags":["admin"]}},"/nermo/vendors/{vendorId}/reactivate":{"post":{"operationId":"AdminReactivateVendor","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorDetail"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Reactivate a vendor tenant.","tags":["admin"]}},"/nermo/vendors/{vendorId}/rebate-agreements":{"get":{"operationId":"AdminListVendorRebateAgreements","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorRebateAgreementList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"A vendor's rebate agreement steps (rate + paymentTermsDays).","tags":["admin"]},"post":{"operationId":"AdminCreateVendorRebateAgreement","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVendorRebateAgreement"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorRebateAgreement"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Append a rebate agreement step (the ladder automation writes these later).","tags":["admin"]}},"/nermo/vendors/{vendorId}/rebate-agreements/re-enroll":{"post":{"operationId":"AdminReEnrollVendorRebateLadder","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorRebateAgreement"}}},"description":"created"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Hand a negotiated vendor back to the completeness ladder (nermo.finance.manage) — appends the current computed ladder rate as source='ladder' at today's date with carried terms.","tags":["admin"]}},"/nermo/vendors/{vendorId}/rebates":{"get":{"operationId":"AdminListVendorRebates","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"in":"query","name":"status","schema":{"enum":["accrued","invoiced","settled","waived"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorRebateList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"A vendor's rebate accruals (the Rebate tab list).","tags":["admin"]}},"/nermo/vendors/{vendorId}/unpublish":{"post":{"operationId":"AdminUnpublishVendor","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorDetail"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Clear the public-page consent flag. The slug is kept — republish never churns the public URL.","tags":["admin"]}},"/nermo/vendors/{vendorId}/users":{"get":{"operationId":"AdminListVendorUsers","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"List users on a vendor tenant.","tags":["admin"]},"post":{"operationId":"AdminCreateVendorUser","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUserOnTenant"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedUserWithTempPassword"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Provision a user on a vendor tenant; returns a one-time temporary password.","tags":["admin"]}},"/nermo/webhook-subscriptions":{"get":{"operationId":"AdminListWebhookSubscriptions","parameters":[{"in":"query","name":"tenantId","schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminWebhookSubscriptionList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List webhook subscriptions across all tenants with delivery health. Requires nermo.webhooks.view.","tags":["admin"]}},"/nermo/webhook-subscriptions/{subscriptionId}/deliveries":{"get":{"operationId":"AdminListWebhookDeliveries","parameters":[{"in":"path","name":"subscriptionId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AdminWebhookDeliveryList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Per-attempt delivery history for one subscription (cross-tenant). Requires nermo.webhooks.view.","tags":["admin"]}},"/onboarding/applications/{reference}":{"get":{"operationId":"GetApplicationStatus","parameters":[{"in":"path","name":"reference","required":true,"schema":{"maxLength":64,"minLength":6,"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationReceipt"}}},"description":"ok"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"security":[],"summary":"Track an application by its high-entropy reference. Public — returns the coarse status enum only, never screening detail.","tags":["onboarding"]}},"/onboarding/client-applications":{"post":{"operationId":"SubmitClientApplication","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientApplicationRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationReceipt"}}},"description":"application received"},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/IntakeUnavailable"}},"security":[],"summary":"Submit a client (vessel-operator) access application. Public — Turnstile-gated, rate-limited. One audited tx creates the pending tenant + satellite + onboarding record + owners + workflow trigger.","tags":["onboarding"]}},"/onboarding/vendor-applications":{"post":{"operationId":"SubmitVendorApplication","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorApplicationRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApplicationReceipt"}}},"description":"application received"},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/IntakeUnavailable"}},"security":[],"summary":"Submit a vendor (port-local supplier) application. Public — Turnstile-gated, rate-limited. One audited tx creates the vendor tenant (status intake) + satellite + onboarding record + owners + workflow trigger.","tags":["onboarding"]}},"/onboarding/verify-email":{"post":{"operationId":"VerifyApplicationEmail","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailVerificationRequest"}}},"required":true},"responses":{"204":{"description":"contact email verified (or already verified — idempotent)"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/VerificationUnavailable"}},"security":[],"summary":"Verify an application's contact email via the signed token minted into the verification mail. Public — POST (not a GET link target, so link-scanner prefetchers cannot auto-verify). Idempotent; an invalid or expired token is an undifferentiated 404.","tags":["onboarding"]}},"/orders":{"get":{"operationId":"ListOrders","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Match against the document references (case-insensitive substring).","in":"query","name":"search","schema":{"maxLength":64,"type":"string"}},{"$ref":"#/components/parameters/DateFromParam"},{"$ref":"#/components/parameters/DateToParam"},{"in":"query","name":"status","schema":{"enum":["awaiting-vendor-acceptance","confirmed","delivered","invoiced","cancelled","declined","retracted"],"type":"string"}},{"in":"query","name":"vesselId","schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List orders in scope.","tags":["procurement"]},"post":{"operationId":"CreateOrder","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateOrder"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Create an order from an accepted quote (repair op, client-callable; accept is the primary creator).","tags":["procurement"]}},"/orders/{orderId}":{"get":{"operationId":"GetOrder","parameters":[{"in":"path","name":"orderId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Order detail including lines and computed client-facing status.","tags":["procurement"]}},"/orders/{orderId}/accept":{"post":{"operationId":"AcceptOrder","parameters":[{"in":"path","name":"orderId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Vendor accepts a pending order (awaiting-vendor-acceptance → confirmed). Runs the deal-finalize cascade. Disallowed transition → 409 order-status-conflict; dead RFQ → 409 rfq-status-conflict.","tags":["procurement"]}},"/orders/{orderId}/cancel":{"post":{"operationId":"CancelOrder","parameters":[{"in":"path","name":"orderId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Cancel an order (status-gated — confirmed only). Disallowed transition → 409 order-status-conflict.","tags":["procurement"]}},"/orders/{orderId}/decline":{"post":{"operationId":"DeclineOrder","parameters":[{"in":"path","name":"orderId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeclineOrder"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Vendor declines a pending order (terminal). Winner quote → declined; RFQ stays re-awardable. Disallowed transition → 409 order-status-conflict.","tags":["procurement"]}},"/orders/{orderId}/documents":{"get":{"operationId":"ListOrderDocuments","parameters":[{"in":"path","name":"orderId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/OrderDocument"},"type":"array"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"The vendor's goods invoices / credit notes attached to this order (either party; superseded rows included, flagged).","tags":["procurement"]}},"/orders/{orderId}/payments":{"post":{"operationId":"RecordOrderPayment","parameters":[{"in":"path","name":"orderId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecordOrderPayment"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderPayment"}}},"description":"recorded"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Record an off-platform goods payment on an order (vendor mark-paid; capped at the order total; staff may record via nermo.finance.manage).","tags":["procurement"]}},"/orders/{orderId}/retract":{"post":{"operationId":"RetractOrder","parameters":[{"in":"path","name":"orderId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Client retracts a pending order before vendor acceptance (terminal). Winner quote → received (re-awardable). Disallowed transition → 409 order-status-conflict.","tags":["procurement"]}},"/permissions":{"get":{"operationId":"ListPermissions","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PermissionList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List the global permission catalogue (assignable to roles).","tags":["identity"]}},"/ports":{"get":{"operationId":"ListPorts","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Text search over name / UN-LOCODE.","in":"query","name":"q","schema":{"maxLength":200,"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PortList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List ports (tenant-agnostic reference data).","tags":["products"]}},"/preferred-vendors":{"get":{"operationId":"ListPreferredVendors","parameters":[{"in":"query","name":"portId","schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreferredVendorList"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List the tenant's preferred-vendor rows (optionally one port's set).","tags":["vendor-directory"]}},"/preferred-vendors/{portId}/{vendorId}":{"delete":{"operationId":"DeletePreferredVendor","parameters":[{"in":"path","name":"portId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"removed (or was not present)"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Remove a vendor from the port's preferred set (idempotent — absent row is a 204 too).","tags":["vendor-directory"]},"put":{"operationId":"UpsertPreferredVendor","parameters":[{"in":"path","name":"portId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PreferredVendor"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Add a vendor to the port's preferred set — declarative on the (port, vendor) key (the coverage-CRUD idiom; no body). Not an active vendor → 400 vendor-not-targetable.","tags":["vendor-directory"]}},"/procurement":{"get":{"operationId":"ListClientJourneys","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Match against the RFQ reference and, once awarded, the order reference (case-insensitive substring).","in":"query","name":"search","schema":{"maxLength":64,"type":"string"}},{"in":"query","name":"journeyStage","schema":{"enum":["draft","sent","quoted","pending-acceptance","ordered","in-transit","delivered","exception","invoiced","closed"],"type":"string"}},{"description":"Filter on the exception flag carried beside the stage (true = the delivery is flagged or a line came in short/subst/damaged, whatever the stage reads).","in":"query","name":"hasException","schema":{"type":"boolean"}},{"in":"query","name":"vesselId","schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProcurementJourneyList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List procurement journeys for the calling client (RFQ-rooted, unified lifecycle).","tags":["procurement"]}},"/product-categories":{"get":{"operationId":"ListProductCategories","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductCategoryList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List product categories (tenant-agnostic reference taxonomy).","tags":["products"]}},"/public/vendors":{"get":{"operationId":"ListPublicVendors","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicVendorList"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"}},"security":[],"summary":"The published-vendor directory — public, no auth.","tags":["public-vendors"]}},"/public/vendors/{slug}":{"get":{"operationId":"GetPublicVendorPage","parameters":[{"in":"path","name":"slug","required":true,"schema":{"maxLength":80,"minLength":1,"pattern":"^[a-z0-9-]+$","type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicVendorPage"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}},"security":[],"summary":"One published vendor's public page.","tags":["public-vendors"]}},"/quotes":{"post":{"operationId":"SubmitQuote","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitQuote"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Vendor submits a quote against an rfq_routing. Inactive/closed vendor → 403 (sanctions screening lands in a later wave).","tags":["procurement"]}},"/quotes/{quoteId}":{"get":{"operationId":"GetQuote","parameters":[{"in":"path","name":"quoteId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Quote detail including lines.","tags":["procurement"]}},"/quotes/{quoteId}/accept":{"post":{"operationId":"AcceptQuote","parameters":[{"in":"path","name":"quoteId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}},"description":"order created"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Client awards a quote (whole-quote). Creates a pending order awaiting vendor acceptance (undoable via retract). Does not reject siblings or close windows — that moves to acceptOrder. Expired quote → 409.","tags":["procurement"]}},"/quotes/{quoteId}/submit":{"post":{"operationId":"SubmitQuoteDraft","parameters":[{"in":"path","name":"quoteId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Submit an EXISTING draft quote (draft → received; dispatch → responded; stamps submittedAt). Distinct from submitQuote, which creates-and-submits in one shot. Non-draft → 409 quote-status-conflict; a draft failing the structured-quote floor (no valid-until or zero lines) → 400 quote-floor-incomplete.","tags":["procurement"]}},"/recipes":{"get":{"operationId":"ListRecipes","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"in":"query","name":"ownership","schema":{"default":"all","enum":["shared","own","all"],"type":"string"}},{"in":"query","name":"status","schema":{"enum":["draft","published","archived"],"type":"string"}},{"description":"Name substring search.","in":"query","name":"q","schema":{"maxLength":200,"type":"string"}},{"description":"Exact match on the free-text recipe category (not the product taxonomy).","in":"query","name":"category","schema":{"type":"string"}},{"description":"Only recipes carrying the tag.","in":"query","name":"dietaryTag","schema":{"enum":["halal","kosher","vegetarian","vegan","pescatarian","no-pork","no-beef"],"type":"string"}},{"description":"Only recipes NOT declaring the allergen.","in":"query","name":"excludeAllergen","schema":{"enum":["celery","crustaceans","eggs","fish","gluten","lupin","milk","molluscs","mustard","nuts","peanuts","sesame","soybeans","sulphites"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RecipeList"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Recipe library — the shared NERMO corpus + the tenant's own recipes (RLS returns both on a tenant pin). Lists default to all statuses; the planner's picker filters status=published.","tags":["galley"]},"post":{"operationId":"CreateRecipe","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRecipe"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recipe"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Create a recipe (always a draft; optional forkFromRecipeId provenance — editing a shared corpus recipe = fork). Unknown units reject with a field error (the RFQ-line fold-or-reject gate).","tags":["galley"]}},"/recipes/{recipeId}":{"get":{"operationId":"GetRecipe","parameters":[{"in":"path","name":"recipeId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recipe"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Recipe detail with ingredients embedded. Corpus rows read shared=true.","tags":["galley"]},"patch":{"operationId":"UpdateRecipe","parameters":[{"in":"path","name":"recipeId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRecipe"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recipe"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Edit an own recipe (attrs + ingredient replace-set; full replace, the updateRfq shape). Own rows only — corpus rows are forked, not edited (→ 400); never status; archived rows → 409 recipe-status-conflict.","tags":["galley"]}},"/recipes/{recipeId}/archive":{"post":{"operationId":"ArchiveRecipe","parameters":[{"in":"path","name":"recipeId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recipe"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Archive an own recipe (draft/published→archived). Own rows only; already-archived → 409 recipe-status-conflict. Slotted plans are guarded by the generation-time re-check, not by archive.","tags":["galley"]}},"/recipes/{recipeId}/publish":{"post":{"operationId":"PublishRecipe","parameters":[{"in":"path","name":"recipeId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Recipe"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Publish a draft recipe (draft→published — only published recipes are slottable). Own rows only; draft-only → 409 recipe-status-conflict.","tags":["galley"]}},"/rfq-dispatches":{"get":{"operationId":"ListRfqDispatches","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Match against the RFQ reference (case-insensitive substring).","in":"query","name":"search","schema":{"maxLength":64,"type":"string"}},{"in":"query","name":"status","schema":{"enum":["sent","viewed","responded","declined","expired","cancelled"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RfqDispatchList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List the vendor's RFQ dispatches (the inbox). Vendor-owned read-model; client identity is structurally absent.","tags":["procurement"]}},"/rfq-dispatches/{rfqDispatchId}":{"get":{"operationId":"GetRfqDispatch","parameters":[{"in":"path","name":"rfqDispatchId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RfqDispatch"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"RFQ dispatch detail (header + projected lines).","tags":["procurement"]}},"/rfq-dispatches/{rfqDispatchId}/decline":{"post":{"operationId":"DeclineRfqDispatch","parameters":[{"in":"path","name":"rfqDispatchId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeclineRfqDispatch"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RfqDispatch"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Decline a routed RFQ dispatch with a structured reason (declinable from sent/viewed only → 409 rfq-dispatch-status-conflict otherwise).","tags":["procurement"]}},"/rfq-dispatches/{rfqDispatchId}/quote-draft":{"put":{"operationId":"SaveQuoteDraft","parameters":[{"in":"path","name":"rfqDispatchId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SaveQuoteDraft"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}},"description":"the saved draft"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Create-or-full-replace the vendor's ONE draft quote against a dispatch (header + lines; drafts are legitimately partial). Dispatch must be sent/viewed → 409 quote-status-conflict otherwise.","tags":["procurement"]}},"/rfq-dispatches/{rfqDispatchId}/view":{"post":{"operationId":"MarkRfqDispatchViewed","parameters":[{"in":"path","name":"rfqDispatchId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RfqDispatch"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Mark a dispatch viewed (flips sent→viewed; idempotent no-op once past sent).","tags":["procurement"]}},"/rfqs":{"get":{"operationId":"ListRfqs","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Match against the document references (case-insensitive substring).","in":"query","name":"search","schema":{"maxLength":64,"type":"string"}},{"in":"query","name":"status","schema":{"enum":["draft","sent","replies-received","awarded","closed","cancelled"],"type":"string"}},{"in":"query","name":"vesselId","schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RfqList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List RFQs in scope.","tags":["procurement"]},"post":{"operationId":"CreateRfq","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRfq"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Rfq"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Submit an RFQ (portal entry point). Free-text lines never block. Non-active vessel → 409 vessel-not-active.","tags":["procurement"]}},"/rfqs/{rfqId}":{"get":{"operationId":"GetRfq","parameters":[{"in":"path","name":"rfqId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Rfq"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"RFQ detail including lines + received-quote summaries.","tags":["procurement"]},"patch":{"operationId":"UpdateRfq","parameters":[{"in":"path","name":"rfqId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRfq"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Rfq"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Edit a draft RFQ (header + lines, full replace). Draft-only — any other state → 409 rfq-status-conflict; a non-active vessel in the edit body → 409 vessel-not-active.","tags":["procurement"]}},"/rfqs/{rfqId}/cancel":{"post":{"operationId":"CancelRfq","parameters":[{"in":"path","name":"rfqId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Rfq"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Cancel an RFQ (pre-award only — draft/sent/replies-received). Disallowed transition → 409 rfq-status-conflict.","tags":["procurement"]}},"/rfqs/{rfqId}/send":{"post":{"operationId":"SendRfq","parameters":[{"in":"path","name":"rfqId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendRfq"}}}},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Rfq"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Send a draft RFQ — flips draft→sent and fans it out to eligible vendors (optionally restricted to vendorIds ∩ eligibility). Draft-only → 409 rfq-status-conflict; a draft with no lines → 400; the RFQ's own vessel non-active → 409 vessel-not-active.","tags":["procurement"]}},"/rfqs/{rfqId}/widen":{"post":{"operationId":"WidenRfq","parameters":[{"in":"path","name":"rfqId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Rfq"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Widen a targeted RFQ to the full port fan-out — the safety valve, enabled once the advisory response window (the ETA-tied respond-by target; no stored deadline) has passed with zero quotes. Re-enqueues the routing workflow without a restriction; only the missing dispatches are created (idempotent backfill). Not widenable (untargeted, quotes received, window still open, or no longer live) → 409 rfq-widen-unavailable.","tags":["procurement"]}},"/roles":{"get":{"operationId":"ListRoles","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"$ref":"#/components/parameters/SortByParam"},{"$ref":"#/components/parameters/SortOrderParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoleList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List roles in the caller's tenant. Requires admin.roles.manage.","tags":["identity"]},"post":{"operationId":"CreateRole","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRole"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Role"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Create a custom role. Requires admin.roles.manage. Reserved permission keys → 400.","tags":["identity"]}},"/roles/{roleId}":{"delete":{"operationId":"DeleteRole","parameters":[{"in":"path","name":"roleId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"deleted"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Archive a role. Requires admin.roles.manage.","tags":["identity"]},"get":{"operationId":"GetRole","parameters":[{"in":"path","name":"roleId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Role"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Role detail.","tags":["identity"]},"patch":{"operationId":"UpdateRole","parameters":[{"in":"path","name":"roleId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateRole"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Role"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Update a role. Requires admin.roles.manage. Reserved permission keys / system roles → 400.","tags":["identity"]}},"/stock-takes/{stockTakeId}":{"delete":{"operationId":"DeleteStockTake","parameters":[{"in":"path","name":"stockTakeId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"deleted"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Delete a draft count (204). A posted count is immutable → 409 stock-take-status-conflict.","tags":["galley"]},"get":{"operationId":"GetStockTake","parameters":[{"in":"path","name":"stockTakeId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTake"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"One stock count with its lines.","tags":["galley"]}},"/stock-takes/{stockTakeId}/lines":{"put":{"operationId":"SetStockTakeLines","parameters":[{"in":"path","name":"stockTakeId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetStockTakeLines"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTake"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Replace a draft count's lines (full replace, atomic; one line per product). Draft-only → 409 stock-take-status-conflict; a different-dimension unit or a negative qty → 400 naming the line.","tags":["galley"]}},"/stock-takes/{stockTakeId}/post":{"post":{"operationId":"PostStockTake","parameters":[{"in":"path","name":"stockTakeId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTakePostResult"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Post the draft count — sets each counted product's on-hand figure on the vessel's stock (a partial count leaves every other product untouched); a line whose product already carries a later count date is skipped as stale-count and reported. Draft-only → 409 stock-take-status-conflict.","tags":["galley"]}},"/unit-observations":{"post":{"operationId":"ReportUnitObservation","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnitObservationCreate"}}},"required":true},"responses":{"204":{"description":"recorded (or silently dropped over the per-tenant cap)"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Fire-and-forget unit-notation observation (raw + optional corrected code).","tags":["products"]}},"/unit-standards":{"get":{"operationId":"ListUnitStandards","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UnitStandardList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Canonical unit codes + approved aliases (tenant-agnostic reference data).","tags":["products"]}},"/users":{"get":{"operationId":"ListUsers","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"$ref":"#/components/parameters/SortByParam"},{"$ref":"#/components/parameters/SortOrderParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List users in the caller's tenant. Requires permission admin.users.manage.","tags":["identity"]},"post":{"operationId":"CreateUser","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateUser"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Create a user in the caller's tenant. Requires admin.users.manage.","tags":["identity"]}},"/users/{userId}":{"delete":{"operationId":"DeleteUser","parameters":[{"in":"path","name":"userId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"deleted"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Deactivate / remove a user. Requires admin.users.manage.","tags":["identity"]},"get":{"operationId":"GetUser","parameters":[{"in":"path","name":"userId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"User detail.","tags":["identity"]},"patch":{"operationId":"UpdateUser","parameters":[{"in":"path","name":"userId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateUser"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/User"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Update a user. Requires admin.users.manage.","tags":["identity"]}},"/users/{userId}/connections":{"get":{"operationId":"ListUserConnections","parameters":[{"in":"path","name":"userId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpConnectionList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"One user's live AI-application connections. Requires admin.users.manage.","tags":["identity"]}},"/users/{userId}/connections/{connectionId}":{"delete":{"operationId":"RevokeUserConnection","parameters":[{"in":"path","name":"userId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"connectionId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"revoked"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/RevokeUnavailable"}},"summary":"Revoke one of a user's AI-application connections (the same Zitadel-first order as the self-scoped revoke). Requires admin.users.manage.","tags":["identity"]}},"/users/{userId}/sessions":{"get":{"operationId":"ListUserSessions","parameters":[{"in":"path","name":"userId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignedInSessionList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"503":{"$ref":"#/components/responses/SessionsUnavailable"}},"summary":"One user's live sessions. Requires admin.users.manage.","tags":["identity"]}},"/users/{userId}/sessions/{sessionId}":{"delete":{"operationId":"RevokeUserSession","parameters":[{"in":"path","name":"userId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"sessionId","required":true,"schema":{"maxLength":64,"minLength":1,"type":"string"}}],"responses":{"204":{"description":"ended"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"},"503":{"$ref":"#/components/responses/RevokeUnavailable"}},"summary":"End one of a user's sessions (the same semantics as the self-scoped revoke). Requires admin.users.manage.","tags":["identity"]}},"/vendor-catalog-items":{"get":{"operationId":"ListVendorCatalogItems","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Text search over name / SKU.","in":"query","name":"search","schema":{"maxLength":200,"type":"string"}},{"description":"Filter by active flag; omit for all.","in":"query","name":"active","schema":{"type":"boolean"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorCatalogItemList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List/search the vendor's own catalog items.","tags":["vendor-catalog"]},"post":{"operationId":"CreateVendorCatalogItem","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorCatalogItemInput"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorCatalogItem"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Add a single catalog item (the server mints the id). Duplicate natural key → 409.","tags":["vendor-catalog"]}},"/vendor-catalog-items/bulk":{"post":{"operationId":"BulkUpsertVendorCatalogItems","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUpsertVendorCatalogItems"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BulkUpsertVendorCatalogItemsResult"}}},"description":"per-item outcomes"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Natural-key create-or-update (vendorSku if present, else lower(name)+unitCode) — the one write surface behind the template upload, the price-list import, and the ERP/service-account feed. deactivateMissing = full-sync.","tags":["vendor-catalog"]}},"/vendor-catalog-items/{itemId}":{"put":{"operationId":"UpdateVendorCatalogItem","parameters":[{"in":"path","name":"itemId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorCatalogItemInput"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorCatalogItem"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Full-replace one catalog item (deactivate = active=false — no separate op).","tags":["vendor-catalog"]}},"/vendor-catalog-items/{itemId}/images/{imageId}":{"delete":{"operationId":"DeleteVendorCatalogItemImage","parameters":[{"in":"path","name":"itemId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"imageId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"deleted"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Delete an image (deleting the primary auto-promotes the next by sort order). Requires vendors.catalog.manage.","tags":["vendor-catalog"]},"put":{"operationId":"UpdateVendorCatalogItemImage","parameters":[{"in":"path","name":"itemId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"path","name":"imageId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorCatalogItemImageUpdate"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorCatalogItemImage"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Set an image as the item's primary (atomically demotes the old primary). Requires vendors.catalog.manage.","tags":["vendor-catalog"]}},"/vendor-catalog/confirm-prices":{"post":{"operationId":"ConfirmVendorCatalogPrices","responses":{"200":{"content":{"application/json":{"schema":{"properties":{"confirmedAt":{"format":"date-time","type":"string"}},"required":["confirmedAt"],"type":"object"}}},"description":"stamped"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Explicitly confirm the whole price list as current (vendors.catalog.manage) — sets the C3 vendor-level stamp without changing any item. Naturally idempotent.","tags":["vendor-catalog"]}},"/vendor-client-terms":{"get":{"operationId":"ListVendorClientTerms","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientTermsOverrideList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List the vendor's per-client goods-terms overrides (admin.company.view; vendor party guard). The client never sees this surface.","tags":["finance"]}},"/vendor-client-terms/{clientTenantId}":{"delete":{"operationId":"DeleteVendorClientTerms","parameters":[{"in":"path","name":"clientTenantId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"deleted"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Delete a per-client goods-terms override (admin.company.manage; vendor party guard).","tags":["finance"]},"put":{"operationId":"UpsertVendorClientTerms","parameters":[{"in":"path","name":"clientTenantId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"properties":{"terms":{"$ref":"#/components/schemas/PaymentTerms"}},"required":["terms"],"type":"object"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientTermsOverride"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Upsert a per-client goods-terms override (admin.company.manage; vendor party guard). PUT-upsert, naturally idempotent.","tags":["finance"]}},"/vendor-compliance-documents":{"get":{"operationId":"ListVendorComplianceDocuments","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"description":"Filter by document status; omit for all.","in":"query","name":"status","schema":{"$ref":"#/components/schemas/ComplianceDocStatus"}},{"description":"Filter by document type; omit for all.","in":"query","name":"docType","schema":{"$ref":"#/components/schemas/ComplianceDocType"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ComplianceDocumentList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List the vendor's own compliance documents.","tags":["compliance"]}},"/vendor-compliance-documents/{docId}":{"delete":{"operationId":"DeleteVendorComplianceDocument","parameters":[{"in":"path","name":"docId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"deleted"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Delete an own document while pending-review/rejected (mis-uploads). Verified documents are history — renewal is a new upload. Other statuses → 409 doc-status-conflict. The stored file is deleted with the row.","tags":["compliance"]}},"/vendor-coverage":{"get":{"operationId":"ListVendorCoverage","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorCoverageOwnList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List the vendor's own port coverage. Requires vendors.coverage.manage.","tags":["onboarding"]}},"/vendor-coverage/{portId}":{"delete":{"operationId":"DeleteVendorCoverage","parameters":[{"in":"path","name":"portId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"deleted"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Delete one own-coverage row. Requires vendors.coverage.manage.","tags":["onboarding"]},"put":{"operationId":"UpsertVendorCoverage","parameters":[{"in":"path","name":"portId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertVendorCoverage"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorCoverage"}}},"description":"upserted"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Upsert one own-coverage row (naturally idempotent; ≥1 active row is a vendor activation gate). 404 on an unknown port. Requires vendors.coverage.manage.","tags":["onboarding"]}},"/vendor-directory":{"get":{"operationId":"ListVendorDirectory","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"in":"query","name":"portId","schema":{"$ref":"#/components/schemas/Uuid"}},{"in":"query","name":"category","schema":{"$ref":"#/components/schemas/CostCategory"}},{"in":"query","name":"q","schema":{"maxLength":200,"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorDirectoryList"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"Search the onboarded-vendor directory — by port (portId, over active coverage), category (category, over coverage costCategories), and product (q — canonical-master search joined to mapped catalog rows, with a name_norm fallback over unmapped rows so a newly-imported vendor stays findable; the raw unmapped text itself is never rendered).","tags":["vendor-directory"]}},"/vendor-directory/{vendorId}":{"get":{"operationId":"GetVendorDirectoryProfile","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorDirectoryProfile"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Vendor profile — identity, primary port, coverage, certification statuses.","tags":["vendor-directory"]}},"/vendor-directory/{vendorId}/catalog":{"get":{"operationId":"ListVendorDirectoryCatalog","parameters":[{"in":"path","name":"vendorId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"in":"query","name":"q","schema":{"maxLength":200,"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorDirectoryCatalog"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"The vendor's price-less catalog view (paged) — canonical-mapped active rows rendered via the canonical master, plus the aggregate count of unmapped rows. Answers \"can this vendor supply these lines\"; prices remain quote-time-only.","tags":["vendor-directory"]}},"/vendor-listing":{"get":{"operationId":"GetVendorListing","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorListing"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"The vendor's own public-listing state. Requires vendors.manage.","tags":["vendor-listing"]}},"/vendor-listing/consent":{"delete":{"operationId":"WithdrawVendorListing","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorListing"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Withdraw consent — unpublishes immediately; the slug is kept. Requires vendors.manage.","tags":["vendor-listing"]},"put":{"operationId":"ConsentVendorListing","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorListing"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Record public-listing consent (declarative; re-PUT keeps the original consent timestamp). Publishes immediately when eligible. Requires vendors.manage.","tags":["vendor-listing"]}},"/vendor-onboarding/checklist":{"get":{"operationId":"GetVendorOnboardingChecklist","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorOnboardingChecklist"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"The vendor's own onboarding-completion checklist (derived — nothing stored). Requires vendors.manage.","tags":["onboarding"]}},"/vendor-payment-methods":{"get":{"operationId":"ListVendorPaymentMethods","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorPaymentMethodList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List the vendor's own payment methods (last-4 + holder name + metadata — never the IBAN or SWIFT). Requires vendors.banking.manage.","tags":["onboarding"]},"post":{"operationId":"CreateVendorPaymentMethod","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVendorPaymentMethodRequest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorPaymentMethod"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Enter the vendor's own banking details (first self-service payment-method write; IBAN + SWIFT envelope-encrypted, the account holder name stored in cleartext; reads return last-4 + holder name + metadata). Requires vendors.banking.manage.","tags":["onboarding"]}},"/vendor-terms":{"get":{"operationId":"GetVendorTerms","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorTerms"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"The vendor's default goods payment terms (admin.company.view; vendor party guard).","tags":["finance"]},"put":{"operationId":"UpdateVendorTerms","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorTerms"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VendorTerms"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Update the vendor's default goods payment terms (admin.company.manage; vendor party guard). Full-state PUT, naturally idempotent.","tags":["finance"]}},"/vessel-groups":{"get":{"operationId":"ListVesselGroups","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"$ref":"#/components/parameters/SortByParam"},{"$ref":"#/components/parameters/SortOrderParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VesselGroupList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List client-defined vessel groups. Requires admin.groups.manage.","tags":["identity"]},"post":{"operationId":"CreateVesselGroup","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVesselGroup"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VesselGroup"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Create a vessel group. Requires admin.groups.manage.","tags":["identity"]}},"/vessel-groups/{groupId}":{"delete":{"operationId":"DeleteVesselGroup","parameters":[{"in":"path","name":"groupId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"deleted"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Delete a vessel group. Requires admin.groups.manage.","tags":["identity"]},"get":{"operationId":"GetVesselGroup","parameters":[{"in":"path","name":"groupId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VesselGroup"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Vessel group detail.","tags":["identity"]},"patch":{"operationId":"UpdateVesselGroup","parameters":[{"in":"path","name":"groupId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateVesselGroup"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VesselGroup"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Update a vessel group. Requires admin.groups.manage.","tags":["identity"]}},"/vessels":{"get":{"operationId":"ListVessels","parameters":[{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"$ref":"#/components/parameters/SortByParam"},{"$ref":"#/components/parameters/SortOrderParam"},{"description":"Optional filter by IMO.","in":"query","name":"imo","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VesselList"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"List vessels in scope. Requires permission vessels.view.","tags":["identity"]},"post":{"operationId":"CreateVessel","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateVessel"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Vessel"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Register a vessel in the caller's tenant. Requires vessels.manage. Adding a vessel starts its subscription: the body carries the recorded fee acceptance (409 subscription-rate-changed on a stale rate); the first add also requires the current Client Agreement accepted (409 client-agreement-required) and the company billing block filled (409 billing-details-required). The vessel is born pending and bills from its activation; under the review-after posture the response already carries status active and the stub is issued. Activation additionally requires a Client Agreement version in force on the account, which this op has already enforced at the add, so the client door never returns a vessel held on that gate. Dunning-paused client billing → 409 client-billing-paused. PORTAL ONLY: the fee acceptance is a person's act in the Client Portal, so a request whose token was not issued to the Client Portal's application is refused with 403 portal-acceptance-required before anything is recorded (staff add through the /nermo twin with a written instrument).","tags":["identity"]}},"/vessels/{vesselId}":{"get":{"operationId":"GetVessel","parameters":[{"in":"path","name":"vesselId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Vessel"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Vessel detail by surrogate id.","tags":["identity"]},"patch":{"operationId":"UpdateVessel","parameters":[{"in":"path","name":"vesselId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateVessel"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Vessel"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Update a vessel's metadata. Requires vessels.manage.","tags":["identity"]}},"/vessels/{vesselId}/allowances":{"get":{"operationId":"ListVesselAllowances","parameters":[{"in":"path","name":"vesselId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllowanceList"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"The vessel's standing daily allowances — the default list a new vessel-bound menu plan copies.","tags":["galley"]},"put":{"operationId":"SetVesselAllowances","parameters":[{"in":"path","name":"vesselId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetAllowances"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AllowanceList"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Replace the vessel's daily allowances (full replace; one line per product; unknown units reject with a field error). Existing plans keep their own snapshot.","tags":["galley"]}},"/vessels/{vesselId}/decommission":{"post":{"operationId":"DecommissionVessel","parameters":[{"in":"path","name":"vesselId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Vessel"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Decommission a vessel. Requires vessels.manage.","tags":["identity"]}},"/vessels/{vesselId}/reactivate":{"post":{"operationId":"ReactivateVessel","parameters":[{"in":"path","name":"vesselId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReactivateVessel"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Vessel"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Reactivate a decommissioned vessel — a fresh fee acceptance (the body); the vessel re-enters pending and bills from its activation. Requires vessels.manage. A declined vessel is refused (409 vessel-not-active — only the staff reactivate op returns one). Dunning-paused client billing → 409 client-billing-paused. PORTAL ONLY, as vessel create: a request whose token was not issued to the Client Portal's application → 403 portal-acceptance-required.","tags":["identity"]}},"/vessels/{vesselId}/stock":{"get":{"operationId":"ListVesselStock","parameters":[{"in":"path","name":"vesselId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VesselStockList"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"The vessel's stock on board — one row per canonical product (on-hand qty in the row's unit of record, last count date, last change + its source), plus the summary the generate dialog shows (product count, oldest count date).","tags":["galley"]}},"/vessels/{vesselId}/stock-takes":{"get":{"operationId":"ListStockTakes","parameters":[{"in":"path","name":"vesselId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"},{"in":"query","name":"status","schema":{"enum":["draft","posted"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTakeList"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"The vessel's stock counts (date, status, line count, who posted), newest first.","tags":["galley"]},"post":{"operationId":"CreateStockTake","parameters":[{"in":"path","name":"vesselId","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateStockTake"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StockTake"}}},"description":"created"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Create a draft stock count for the vessel (countedAt defaults to today; optional lines — canonical products only, a different-dimension unit rejects with a field error naming the line). A quick adjust of one product is a one-line count posted at once.","tags":["galley"]}},"/webhook-deliveries/{id}/replay":{"post":{"operationId":"ReplayWebhookDelivery","parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDelivery"}}},"description":"replay scheduled — the fresh chain's first attempt row"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Replay one delivery: schedules a fresh attempt chain carrying the SAME eventId (consumer dedupe intact). 409 when the subscription is inactive (subscription-inactive) or unverified (subscription-unverified), or the source event is past retention (event-expired). Requires webhooks.manage.","tags":["webhooks"]}},"/webhook-subscriptions":{"get":{"operationId":"ListWebhookSubscriptions","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"}},"summary":"The tenant's webhook subscriptions (secret-free projection). Requires webhooks.view.","tags":["webhooks"]},"post":{"operationId":"CreateWebhookSubscription","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionCreate"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionCreated"}}},"description":"created — the secret is shown exactly once"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Register a webhook endpoint. The response carries the one-time signing secret; a verification challenge is POSTed to the URL — deliveries start only after the endpoint echoes the token. Requires webhooks.manage.","tags":["webhooks"]}},"/webhook-subscriptions/{id}":{"patch":{"operationId":"UpdateWebhookSubscription","parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscriptionUpdate"}}},"required":true},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscription"}}},"description":"ok"},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Update a subscription. A URL change resets verification and re-arms the challenge — it shares the resend cooldown window (429 challenge-cooldown), since both issue a challenge; event types are re-validated against the party's subscribable vocabulary. An explicit null label clears it; omitting the key leaves it unchanged. Requires webhooks.manage.","tags":["webhooks"]}},"/webhook-subscriptions/{id}/close-rotation":{"post":{"operationId":"CloseWebhookRotation","parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"Rotation window closed"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Close an open rotation window — the previous secret stops signing. Idempotent (closing an already-closed window is still 204). Requires webhooks.manage.","tags":["webhooks"]}},"/webhook-subscriptions/{id}/deliveries":{"get":{"operationId":"ListWebhookDeliveries","parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/PageParam"},{"$ref":"#/components/parameters/PageSizeParam"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookDeliveryList"}}},"description":"ok"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}},"summary":"Delivery history for one subscription, newest first (retention-bounded). Requires webhooks.view.","tags":["webhooks"]}},"/webhook-subscriptions/{id}/resend-challenge":{"post":{"operationId":"ResendWebhookChallenge","parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}}],"responses":{"204":{"description":"Challenge issued"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Re-send the verification challenge to an unverified endpoint. Rate-limited by a cooldown window (429 challenge-cooldown). Requires webhooks.manage.","tags":["webhooks"]}},"/webhook-subscriptions/{id}/rotate":{"post":{"operationId":"RotateWebhookSecret","parameters":[{"in":"path","name":"id","required":true,"schema":{"$ref":"#/components/schemas/Uuid"}},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSecret"}}},"description":"rotated — the new secret is shown exactly once"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"summary":"Rotate the signing secret. The new secret is shown once; the previous secret keeps co-signing (dual-signature window) until close-rotation or the auto-close sweep. 409 idempotency-conflict when the key was already used for a different request. Requires webhooks.manage.","tags":["webhooks"]}}},"security":[{"bearerAuth":[]}],"servers":[{"description":"production","url":"https://api.nermo.io/api/v1"},{"description":"local","url":"http://localhost:8080/api/v1"}],"tags":[{"name":"auth"},{"name":"identity"},{"name":"fleet"},{"name":"admin"},{"name":"products"},{"name":"procurement"},{"name":"matching"},{"name":"finance"},{"name":"galley"},{"name":"nermo-recipes"},{"name":"compliance"},{"name":"onboarding"},{"name":"reputation"},{"name":"vendor-directory"},{"name":"webhooks"}]}