Skip to Content
APIOpenapiRgl8r Public API V1.2.0

Rgl8r Public API V1.2.0

Source: docs/api/openapi/rgl8r-public-api-v1.2.0.yaml

openapi: 3.1.0 info: title: RGL8R Public API version: 1.2.0 summary: Public API contract for integration-key clients and AI agents. description: | Versioned contract for the public integration surface used by no-code automations, CI pipelines, and AI agents. Included surfaces: - Public self-serve signup session lifecycle - Public billing setup session bootstrap (integration-auth) - Integration-key token exchange - Async job polling - SIMA screening queue + reporting - Upload enqueue endpoints - TRADE durable feed sessions (upload -> normalize -> classify-preview -> review -> apply) - SHIP finding workflow (list + state transitions + batch) - SHIP claim submission workflow (DRAFT -> READY -> SUBMITTED -> ACKNOWLEDGED -> CREDIT_CONFIRMED) Out of scope for v1.2.0: - Pure API tenant bootstrap (onboarding is operator-assisted / Clerk-gated) - Integration-key lifecycle (create/list/revoke/rotate are Clerk-only admin routes) - Admin/ref routes, tenant admin routes, and ops-only endpoints - Non-blocking trade-feed issue acknowledgement (Clerk-only operator handoff; see docs/api/trade-feed-contract-v1.md PREVIEW_BLOCKED section) - Automated carrier rate-sync Workflow semantics that YAML cannot express are documented in companion contracts: - docs/api/trade-feed-contract-v1.md (TRADE feed state machine + handoff) - docs/api/ship-finding-contract-v1.md (SHIP finding state machine) - docs/api/ship-claim-submission-contract-v1.md (SHIP claim submission state machine) Compatibility policy is defined in docs/api/public-api-contract-v1.md. servers: - url: https://api.rgl8r.com description: Production - url: https://rgl8r-staging-api.onrender.com description: Staging (safe sandbox) security: - bearerAuth: [] tags: - name: Auth description: Authenticate integration-key clients and issue short-lived bearer tokens. - name: Public Signup description: Unauthenticated signup bootstrap and resumable tenant provisioning. - name: Billing description: Billing setup and payment-method bootstrap for authenticated tenant integrations. - name: Jobs description: Poll asynchronous processing state across upload and screening workflows. - name: Trade Screening description: Run SIMA screening and retrieve outcomes, evidence, summaries, and exports. - name: Uploads description: Enqueue CSV/Excel files for trade, catalog, ship, order, and carrier workflows. - name: Trade Feeds description: Durable TRADE feed sessions with upload, normalization, classify-preview, review, and apply. - name: Ship Findings description: SHIP finding workflow state transitions (dispute, dismiss, submit, carrier-review, credit, reject, reopen). - name: Ship Claims description: SHIP claim submission lifecycle (DRAFT, READY, SUBMITTED, ACKNOWLEDGED, CREDIT_CONFIRMED). paths: /api/public/signup/sessions: post: tags: [Public Signup] operationId: createPublicSignupSession summary: Create a self-serve signup session and send a magic link plus fallback code security: [] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PublicSignupSessionCreateRequest' responses: '201': description: Signup session created content: application/json: schema: $ref: '#/components/schemas/PublicSignupSessionCreateResponse' '400': $ref: '#/components/responses/InvalidRequestError' '429': $ref: '#/components/responses/RateLimitedError' '503': $ref: '#/components/responses/FeatureDisabledError' '500': $ref: '#/components/responses/InternalError' /api/public/signup/sessions/{sessionId}/verify-email: post: tags: [Public Signup] operationId: verifyPublicSignupSessionEmail summary: Verify the fallback 6-digit code and advance the session to EMAIL_VERIFIED security: [] parameters: - name: sessionId in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: type: object required: [code] properties: code: type: string pattern: '^\d{6}$' responses: '200': description: Session email verified content: application/json: schema: $ref: '#/components/schemas/PublicSignupSessionStatusResponse' '400': $ref: '#/components/responses/InvalidRequestError' '404': $ref: '#/components/responses/NotFoundError' '429': $ref: '#/components/responses/RateLimitedError' '503': $ref: '#/components/responses/FeatureDisabledError' '500': $ref: '#/components/responses/InternalError' /api/public/signup/sessions/{sessionId}/verify-magic-link: post: tags: [Public Signup] operationId: verifyPublicSignupSessionMagicLink summary: Verify the emailed magic link token and advance the session to EMAIL_VERIFIED security: [] parameters: - name: sessionId in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: type: object required: [token] properties: token: type: string responses: '200': description: Session email verified content: application/json: schema: $ref: '#/components/schemas/PublicSignupSessionStatusResponse' '400': $ref: '#/components/responses/InvalidRequestError' '404': $ref: '#/components/responses/NotFoundError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/public/signup/sessions/{sessionId}/resend-verification: post: tags: [Public Signup] operationId: resendPublicSignupSessionVerification summary: Resend the signup verification magic link and fallback code security: [] parameters: - name: sessionId in: path required: true schema: type: string format: uuid responses: '200': description: Verification challenge reissued content: application/json: schema: $ref: '#/components/schemas/PublicSignupSessionCreateResponse' '400': $ref: '#/components/responses/InvalidRequestError' '404': $ref: '#/components/responses/NotFoundError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '503': $ref: '#/components/responses/FeatureDisabledError' '500': $ref: '#/components/responses/InternalError' /api/public/signup/sessions/{sessionId}/provision: post: tags: [Public Signup] operationId: provisionPublicSignupSession summary: Provision tenant resources with resumable state-machine semantics security: [] parameters: - name: sessionId in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PublicSignupProvisionRequest' responses: '201': description: Provisioning completed content: application/json: schema: $ref: '#/components/schemas/PublicSignupProvisionResponse' '202': description: Provisioning in-progress or manual-review path content: application/json: schema: $ref: '#/components/schemas/PublicSignupProvisionResponse' '400': $ref: '#/components/responses/InvalidRequestError' '404': $ref: '#/components/responses/NotFoundError' '409': $ref: '#/components/responses/ActionNotAllowedError' '503': $ref: '#/components/responses/FeatureDisabledError' '500': $ref: '#/components/responses/InternalError' /api/public/signup/sessions/{sessionId}: get: tags: [Public Signup] operationId: getPublicSignupSession summary: Get current signup session status security: [] parameters: - name: sessionId in: path required: true schema: type: string format: uuid responses: '200': description: Signup session snapshot content: application/json: schema: $ref: '#/components/schemas/PublicSignupSessionGetResponse' '400': $ref: '#/components/responses/InvalidRequestError' '404': $ref: '#/components/responses/NotFoundError' '503': $ref: '#/components/responses/FeatureDisabledError' '500': $ref: '#/components/responses/InternalError' /api/public/billing/setup-session: post: tags: [Billing] operationId: createPublicBillingSetupSession summary: Create a Stripe SetupIntent for tenant billing setup description: | Creates or reuses the tenant Stripe customer and returns a SetupIntent client secret. Requires an integration bearer token and an active tenant. requestBody: required: false content: application/json: schema: $ref: '#/components/schemas/BillingSetupSessionRequest' responses: '201': description: Billing setup session created content: application/json: schema: $ref: '#/components/schemas/BillingSetupSessionResponse' '401': $ref: '#/components/responses/AuthError' '403': $ref: '#/components/responses/PrincipalDeniedError' '500': $ref: '#/components/responses/InternalError' /api/auth/token/integration: post: tags: [Auth] operationId: createIntegrationToken summary: Start an integration session (API key to bearer token) description: | Use this endpoint as step 1 in every integration flow. Exchange a valid integration key (`x-api-key`) for a short-lived JWT bearer token, then call tenant endpoints with `Authorization: Bearer <token>`. security: - integrationKeyHeader: [] responses: '200': description: Token issued content: application/json: schema: $ref: '#/components/schemas/IntegrationTokenResponse' examples: success: value: access_token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... token_type: Bearer expires_in: 3600 default_adapter: catalog-excel '400': $ref: '#/components/responses/MissingApiKeyError' '401': $ref: '#/components/responses/AuthError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/jobs: get: tags: [Jobs] operationId: listJobs summary: Monitor async jobs for the authenticated tenant description: | Use this endpoint to monitor queue activity after upload/screening enqueue calls. Supports filtering by job type and status for dashboards, polling loops, and alerts. parameters: - name: type in: query description: | Optional job type filter. Public integrations should use `catalog_upload` for trade catalog jobs. (Legacy upload type values may still appear in older data but are not part of this public contract.) schema: type: string enum: - ship_upload - sima_validation - catalog_upload - order_upload - carrier_agreement_upload - notification_digest - notification_event - name: status in: query description: Optional status filter. schema: type: string enum: [PENDING, PROCESSING, COMPLETED, FAILED] - name: limit in: query schema: type: integer minimum: 1 maximum: 100 default: 50 - name: offset in: query schema: type: integer minimum: 0 default: 0 responses: '200': description: Job list content: application/json: schema: $ref: '#/components/schemas/JobsListResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/jobs/{id}: get: tags: [Jobs] operationId: getJobById summary: Get one async job by ID description: | Poll this endpoint until the job reaches a terminal state (`COMPLETED` or `FAILED`). This is the canonical status endpoint for enqueue-based workflows. parameters: - name: id in: path required: true schema: type: string format: uuid responses: '200': description: Job detail content: application/json: schema: $ref: '#/components/schemas/JobResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/sima/batch: post: tags: [Trade Screening] operationId: enqueueSimaBatch summary: Queue SIMA screening for catalog SKUs description: | Creates an async SIMA validation job for selected or all SKUs in the tenant catalog. Use this as step 2 in the canonical screening flow. `screeningAuthority` is optional: - omitted or blank: defaults to CA in downstream workers - provided: must normalize to `CA` or `US` parameters: - $ref: '#/components/parameters/IdempotencyKeyHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/SimaBatchRequest' examples: allSkus: value: skus: null runPolicy: always screeningAuthority: US responses: '202': description: Job accepted content: application/json: schema: $ref: '#/components/schemas/JobAcceptedResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/sima/results: get: tags: [Trade Screening] operationId: listSimaResults summary: Retrieve SIMA outcomes with workflow filters description: | Returns screened SKU outcomes (`AT_RISK`, `NEEDS_REVIEW`, `CLEARED`) plus workflow state. Use this for flagged queues, analyst review surfaces, and downstream automations. parameters: - name: outcome in: query schema: type: string enum: [AT_RISK, NEEDS_REVIEW, CLEARED] - name: workflow in: query schema: type: string enum: [OPEN, OVERRIDDEN, ACCEPTED, DISMISSED] - name: workflow_not in: query schema: type: string enum: [OPEN, OVERRIDDEN, ACCEPTED, DISMISSED] - name: hs_in_scope in: query schema: type: boolean - name: is_stale in: query schema: type: boolean - name: limit in: query schema: type: integer minimum: 1 maximum: 1000 default: 50 - name: offset in: query schema: type: integer minimum: 0 default: 0 - name: cursor in: query schema: type: string responses: '200': description: SIMA outcomes content: application/json: schema: $ref: '#/components/schemas/SimaResultsResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/sima/summary: get: tags: [Trade Screening] operationId: getSimaSummary summary: Get SIMA KPI summary counts description: | Returns aggregate KPI counts for SIMA outcomes and workflow buckets. Use this endpoint to power dashboard cards and high-level risk rollups. parameters: - name: outcome in: query schema: type: string enum: [AT_RISK, NEEDS_REVIEW, CLEARED] - name: workflow in: query schema: type: string enum: [OPEN, OVERRIDDEN, ACCEPTED, DISMISSED] - name: workflow_not in: query schema: type: string enum: [OPEN, OVERRIDDEN, ACCEPTED, DISMISSED] responses: '200': description: SIMA summary content: application/json: schema: $ref: '#/components/schemas/SimaSummaryResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/sima/results/{sku}/evidence: get: tags: [Trade Screening] operationId: getSimaEvidenceBySku summary: Explain one SKU screening decision description: | Returns detailed evidence for a SKU outcome, including classification context, matched signals, and workflow/audit trail data used for analyst review. parameters: - name: sku in: path required: true schema: type: string responses: '200': description: Evidence payload content: application/json: schema: $ref: '#/components/schemas/SimaEvidenceResponse' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/sima/report: get: tags: [Trade Screening] operationId: exportSimaReport summary: Export SIMA report data (JSON or CSV) description: | Export SIMA outcomes for BI tools, spreadsheets, and downstream reconciliation. Returns JSON by default or CSV when `format=csv`. parameters: - name: format in: query schema: type: string enum: [json, csv] default: json - name: filter in: query schema: type: string enum: [flagged, covered] - name: status in: query schema: type: string responses: '200': description: Report export content: application/json: schema: $ref: '#/components/schemas/SimaReportJsonResponse' text/csv: schema: type: string '401': $ref: '#/components/responses/AuthError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/upload: post: tags: [Uploads] operationId: enqueueTradeUpload summary: Upload a trade catalog file for async processing description: | Enqueues a trade catalog file and returns a job ID for async processing. Use the returned `jobId` with `/api/jobs/{id}` until terminal status. parameters: - $ref: '#/components/parameters/IdempotencyKeyHeader' requestBody: required: true content: multipart/form-data: schema: $ref: '#/components/schemas/TradeUploadRequest' responses: '202': description: Upload accepted content: application/json: schema: $ref: '#/components/schemas/UploadAcceptedResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '413': $ref: '#/components/responses/FileTooLargeError' '415': $ref: '#/components/responses/InvalidFileFormatError' '500': $ref: '#/components/responses/InternalError' /api/catalog/upload: post: tags: [Uploads] operationId: enqueueCatalogUpload summary: Upload a product catalog for screening pipelines description: | Enqueues product catalog ingestion and normalization. Use this when your integration starts from SKU catalog files before SIMA screening. parameters: - $ref: '#/components/parameters/IdempotencyKeyHeader' requestBody: required: true content: multipart/form-data: schema: $ref: '#/components/schemas/CatalogUploadRequest' responses: '202': description: Upload accepted content: application/json: schema: $ref: '#/components/schemas/CatalogUploadAcceptedResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '413': $ref: '#/components/responses/FileTooLargeError' '415': $ref: '#/components/responses/InvalidFileFormatError' '500': $ref: '#/components/responses/InternalError' /api/ship/upload: post: tags: [Uploads] operationId: enqueueShipUpload summary: Upload shipment/invoice CSV for freight audit description: | Enqueues shipment and invoice data for async freight-audit processing. Optional `adapter_id` is used for integration-key adapter allowlist enforcement. requestBody: required: true content: multipart/form-data: schema: type: object required: [file] properties: file: type: string format: binary adapter_id: type: string description: Optional adapter identifier for integration-key adapter allowlist checks. responses: '202': description: Upload accepted content: application/json: schema: $ref: '#/components/schemas/ShipUploadAcceptedResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '403': $ref: '#/components/responses/AdapterNotAllowedError' '409': $ref: '#/components/responses/DuplicateUploadError' '429': $ref: '#/components/responses/RateLimitedError' '413': $ref: '#/components/responses/FileTooLargeError' '415': $ref: '#/components/responses/InvalidFileFormatError' '500': $ref: '#/components/responses/InternalError' /api/orders/upload: post: tags: [Uploads] operationId: enqueueOrdersUpload summary: Upload order CSV for order-side enrichment/reconciliation description: | Enqueues order ingestion used by downstream shipment/order workflows. `ingestMode` controls patch vs full-snapshot behavior. requestBody: required: true content: multipart/form-data: schema: type: object required: [file] properties: file: type: string format: binary ingestMode: type: string enum: [PATCH, SNAPSHOT] responses: '202': description: Upload accepted content: application/json: schema: $ref: '#/components/schemas/OrderUploadAcceptedResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '409': $ref: '#/components/responses/DuplicateUploadError' '429': $ref: '#/components/responses/RateLimitedError' '413': $ref: '#/components/responses/FileTooLargeError' '415': $ref: '#/components/responses/InvalidFileFormatError' '500': $ref: '#/components/responses/InternalError' /api/carrier/agreements/upload: post: tags: [Uploads] operationId: enqueueCarrierAgreementUpload summary: Upload carrier agreement CSV for contract-rate checks description: | Enqueues carrier agreement ingestion used to validate billed rates against contract terms. Requires `ship:upload` scope for integration principals. x-rgl8r-required-scopes: ['ship:upload'] requestBody: required: true content: multipart/form-data: schema: type: object required: [file] properties: file: type: string format: binary responses: '202': description: Upload accepted content: application/json: schema: $ref: '#/components/schemas/ShipUploadAcceptedResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '403': $ref: '#/components/responses/ScopeDeniedError' '409': $ref: '#/components/responses/DuplicateUploadError' '429': $ref: '#/components/responses/RateLimitedError' '413': $ref: '#/components/responses/FileTooLargeError' '415': $ref: '#/components/responses/InvalidFileFormatError' '500': $ref: '#/components/responses/InternalError' /api/trade/feeds/upload: post: tags: [Trade Feeds] operationId: uploadTradeFeed summary: Upload a trade feed file into a durable feed session description: | Creates a durable TRADE feed session from a CSV or Excel file. The session proceeds through `UPLOADED -> NORMALIZING -> READY | PREVIEW_BLOCKED | FAILED` and then `READY -> APPLIED`. Idempotency: replay behavior is opt-in via the `Idempotency-Key` header only. Without the header, the same file uploaded twice creates two independent sessions. With the header, a retry using the same key and the same normalized payload returns the original session with `replayed: true`. A different payload under the same key returns `409 ACTION_NOT_ALLOWED`. See `docs/api/trade-feed-contract-v1.md` for the full state machine, issue handling, and the `PREVIEW_BLOCKED` operator handoff. parameters: - $ref: '#/components/parameters/IdempotencyKeyHeader' requestBody: required: true content: multipart/form-data: schema: $ref: '#/components/schemas/TradeFeedUploadRequest' responses: '202': description: | Feed session accepted for asynchronous normalization. The session and the normalization job are created synchronously; normalization itself runs in the background. Callers must poll `GET /api/trade/feeds/{sessionId}` until `status` reaches `READY`, `PREVIEW_BLOCKED`, or `FAILED`. content: application/json: schema: $ref: '#/components/schemas/TradeFeedUploadAcceptedResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '409': $ref: '#/components/responses/ActionNotAllowedError' '413': $ref: '#/components/responses/FileTooLargeError' '415': $ref: '#/components/responses/InvalidFileFormatError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/trade/feeds/{sessionId}: get: tags: [Trade Feeds] operationId: getTradeFeedSession summary: Get a trade feed session with normalization progress parameters: - name: sessionId in: path required: true schema: type: string format: uuid responses: '200': description: Trade feed session detail content: application/json: schema: $ref: '#/components/schemas/TradeFeedSessionResponse' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/trade/feeds/{sessionId}/skus: get: tags: [Trade Feeds] operationId: listTradeFeedSkus summary: Paginated SKU list for a normalized feed session parameters: - name: sessionId in: path required: true schema: type: string format: uuid - name: limit in: query schema: type: integer minimum: 1 maximum: 500 default: 50 - name: offset in: query schema: type: integer minimum: 0 default: 0 responses: '200': description: SKU page content: application/json: schema: $ref: '#/components/schemas/TradeFeedSkuListResponse' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/trade/feeds/{sessionId}/issues: get: tags: [Trade Feeds] operationId: listTradeFeedIssues summary: Paginated issues list for a feed session description: | Lists blocking and non-blocking issues surfaced during normalization. Blocking issues must be resolved to leave `PREVIEW_BLOCKED`. Non-blocking issues can be acknowledged by an operator (Clerk-only route, not part of this public contract) — see `docs/api/trade-feed-contract-v1.md`. parameters: - name: sessionId in: path required: true schema: type: string format: uuid - name: limit in: query schema: type: integer minimum: 1 maximum: 500 default: 50 - name: offset in: query schema: type: integer minimum: 0 default: 0 responses: '200': description: Issue page content: application/json: schema: $ref: '#/components/schemas/TradeFeedIssueListResponse' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/trade/feeds/{sessionId}/classify-preview: post: tags: [Trade Feeds] operationId: enqueueTradeFeedClassifyPreview summary: Enqueue a preview classification job for a feed session description: | Starts the preview classification flow for the feed session. Each tenant has an in-flight preview-job limit; exceeding it returns `429 RATE_LIMITED`. Returns a `jobId` that can be polled through `/api/jobs/:id`, and a `previewJobId` for the subsequent review step. parameters: - name: sessionId in: path required: true schema: type: string format: uuid responses: '202': description: Preview classification job accepted content: application/json: schema: $ref: '#/components/schemas/TradeFeedPreviewEnqueueResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/trade/classify/preview/{jobId}/review: put: tags: [Trade Feeds] operationId: reviewTradeClassifyPreview summary: Persist reviewed preview snapshot for staleness detection on apply parameters: - name: jobId in: path required: true schema: type: string format: uuid requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TradeClassifyPreviewReviewRequest' responses: '200': description: Reviewed snapshot persisted content: application/json: schema: $ref: '#/components/schemas/TradeClassifyPreviewReviewResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/trade/feeds/{sessionId}/apply: post: tags: [Trade Feeds] operationId: applyTradeFeedSession summary: Apply a normalized feed session to the catalog description: | Applies the reviewed/normalized feed session to the tenant catalog. Fails with `409 ACTION_NOT_ALLOWED` if the reviewed preview snapshot is stale relative to the current normalized artifact. parameters: - name: sessionId in: path required: true schema: type: string format: uuid responses: '200': description: Feed session applied content: application/json: schema: $ref: '#/components/schemas/TradeFeedSessionResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/ship/audit: get: tags: [Ship Audit] operationId: listShipAudit summary: List shipments with rollup outcomes and explanation summaries description: | Paginated shipment audit list. Each row includes a compact narrative summary derived from the shipment's findings and latest rate-engine calculation. parameters: - name: limit in: query schema: type: integer minimum: 1 maximum: 200 - name: offset in: query schema: type: integer minimum: 0 - name: carrier in: query schema: type: string - name: outcome in: query schema: type: string enum: [AT_RISK, CLEAR, NEEDS_REVIEW] responses: '200': description: Paginated audit list content: application/json: schema: type: object properties: shipments: type: array items: $ref: '#/components/schemas/ShipAuditRow' total: type: integer limit: type: integer offset: type: integer hasMore: type: boolean '401': $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalError' /api/ship/shipments/{shipmentId}: get: tags: [Ship Audit] operationId: getShipmentDetail summary: Shipment detail with findings and explanation narrative parameters: - name: shipmentId in: path required: true schema: type: string format: uuid responses: '200': description: Shipment detail with embedded findings content: application/json: schema: type: object properties: shipment: $ref: '#/components/schemas/ShipShipmentDetail' findings: type: array items: $ref: '#/components/schemas/ShipFindingItem' '404': $ref: '#/components/responses/NotFoundError' '401': $ref: '#/components/responses/UnauthorizedError' '500': $ref: '#/components/responses/InternalError' /api/ship/findings: get: tags: [Ship Findings] operationId: listShipFindings summary: List SHIP findings with filters and status counts parameters: - name: workflowStatus in: query schema: type: string enum: [OPEN, DISPUTED, SUBMITTED, CARRIER_REVIEW, CREDITED, REJECTED, DISMISSED] - name: limit in: query schema: type: integer minimum: 1 maximum: 500 default: 50 - name: offset in: query schema: type: integer minimum: 0 default: 0 responses: '200': description: Finding page content: application/json: schema: $ref: '#/components/schemas/ShipFindingListResponse' '401': $ref: '#/components/responses/AuthError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/ship/findings/batch: post: tags: [Ship Findings] operationId: batchShipFindings summary: Batch workflow action on SHIP findings description: | Apply one workflow action to many findings in a single request. `action=credit` is disabled on this endpoint; use the per-finding credit endpoint instead (see the SHIP Credit Confirmation Contract in `docs/api/public-api-contract-v1.md`). requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ShipFindingBatchRequest' responses: '200': description: Batch action result content: application/json: schema: $ref: '#/components/schemas/ShipFindingBatchResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/ship/findings/{findingId}/dispute: post: tags: [Ship Findings] operationId: disputeShipFinding summary: Transition a finding from OPEN to DISPUTED parameters: - $ref: '#/components/parameters/FindingIdPath' responses: '200': description: Finding transitioned content: application/json: schema: $ref: '#/components/schemas/ShipFindingResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/ship/findings/{findingId}/dismiss: post: tags: [Ship Findings] operationId: dismissShipFinding summary: Transition a finding from OPEN to DISMISSED parameters: - $ref: '#/components/parameters/FindingIdPath' responses: '200': description: Finding transitioned content: application/json: schema: $ref: '#/components/schemas/ShipFindingResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/ship/findings/{findingId}/submit: post: tags: [Ship Findings] operationId: submitShipFinding summary: Transition a finding from DISPUTED to SUBMITTED parameters: - $ref: '#/components/parameters/FindingIdPath' responses: '200': description: Finding transitioned content: application/json: schema: $ref: '#/components/schemas/ShipFindingResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/ship/findings/{findingId}/carrier-review: post: tags: [Ship Findings] operationId: carrierReviewShipFinding summary: Transition a finding from SUBMITTED to CARRIER_REVIEW parameters: - $ref: '#/components/parameters/FindingIdPath' responses: '200': description: Finding transitioned content: application/json: schema: $ref: '#/components/schemas/ShipFindingResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/ship/findings/{findingId}/credit: post: tags: [Ship Findings] operationId: creditShipFinding summary: Record a carrier-confirmed credit on a finding (billing-critical) description: | Records carrier-confirmed credit against a finding and transitions the finding into the terminal `CREDITED` state. Batch credit is not permitted — use this per-finding endpoint. See the SHIP Credit Confirmation Contract in `docs/api/public-api-contract-v1.md`. parameters: - $ref: '#/components/parameters/FindingIdPath' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ShipFindingCreditRequest' responses: '200': description: Credit recorded; finding transitioned content: application/json: schema: $ref: '#/components/schemas/ShipFindingResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/ship/findings/{findingId}/reject: post: tags: [Ship Findings] operationId: rejectShipFinding summary: Transition a finding to the terminal REJECTED state parameters: - $ref: '#/components/parameters/FindingIdPath' responses: '200': description: Finding transitioned content: application/json: schema: $ref: '#/components/schemas/ShipFindingResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/ship/findings/{findingId}/reopen: post: tags: [Ship Findings] operationId: reopenShipFinding summary: Reopen a finding from a terminal or in-flight state back to OPEN parameters: - $ref: '#/components/parameters/FindingIdPath' responses: '200': description: Finding reopened content: application/json: schema: $ref: '#/components/schemas/ShipFindingResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/ship/claims/submissions: get: tags: [Ship Claims] operationId: listShipClaimSubmissions summary: List SHIP claim submissions parameters: - name: status in: query schema: type: string enum: [DRAFT, READY, SUBMITTED, ACKNOWLEDGED, CREDIT_CONFIRMED, CLOSED, FAILED] - name: limit in: query schema: type: integer minimum: 1 maximum: 200 default: 50 - name: offset in: query schema: type: integer minimum: 0 default: 0 responses: '200': description: Claim submissions page content: application/json: schema: $ref: '#/components/schemas/ShipClaimSubmissionListResponse' '401': $ref: '#/components/responses/AuthError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' post: tags: [Ship Claims] operationId: createShipClaimSubmission summary: Create a SHIP claim submission from findings requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ShipClaimSubmissionCreateRequest' responses: '201': description: Claim submission created in DRAFT state content: application/json: schema: $ref: '#/components/schemas/ShipClaimSubmissionResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/ship/claims/submissions/{submissionId}: get: tags: [Ship Claims] operationId: getShipClaimSubmission summary: Get a SHIP claim submission by ID parameters: - $ref: '#/components/parameters/SubmissionIdPath' responses: '200': description: Claim submission detail content: application/json: schema: $ref: '#/components/schemas/ShipClaimSubmissionResponse' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/ship/claims/submissions/{submissionId}/generate-packet: post: tags: [Ship Claims] operationId: generateShipClaimSubmissionPacket summary: Generate a claim packet and transition DRAFT -> READY parameters: - $ref: '#/components/parameters/SubmissionIdPath' responses: '200': description: Packet generated; submission moved to READY content: application/json: schema: $ref: '#/components/schemas/ShipClaimSubmissionResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/ship/claims/submissions/{submissionId}/submit: post: tags: [Ship Claims] operationId: submitShipClaimSubmission summary: Submit a READY claim submission to the carrier parameters: - $ref: '#/components/parameters/SubmissionIdPath' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ShipClaimSubmissionReferenceBody' responses: '200': description: Claim submission moved to SUBMITTED content: application/json: schema: $ref: '#/components/schemas/ShipClaimSubmissionResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/ship/claims/submissions/{submissionId}/acknowledge: post: tags: [Ship Claims] operationId: acknowledgeShipClaimSubmission summary: Record carrier receipt; SUBMITTED -> ACKNOWLEDGED parameters: - $ref: '#/components/parameters/SubmissionIdPath' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ShipClaimSubmissionReferenceBody' responses: '200': description: Claim submission acknowledged content: application/json: schema: $ref: '#/components/schemas/ShipClaimSubmissionResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' /api/ship/claims/submissions/{submissionId}/confirm-credit: post: tags: [Ship Claims] operationId: confirmShipClaimSubmissionCredit summary: Confirm carrier credit for a claim submission description: | Record carrier credit confirmation for a claim submission. Callable from `SUBMITTED`, `ACKNOWLEDGED`, or `FAILED` (retry path). The outcome depends on per-finding credit confirmation results: - **All findings credited** — submission transitions to `CREDIT_CONFIRMED`. - **All findings failed** — submission transitions to `FAILED` (with `failedAt` and `failureReason`). - **Partial** — submission stays at its current status (`SUBMITTED` or `ACKNOWLEDGED`). Because `FAILED` is a valid entry state, callers can retry `confirm-credit` after a previous failure without resetting the submission. parameters: - $ref: '#/components/parameters/SubmissionIdPath' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ShipClaimSubmissionConfirmCreditRequest' responses: '200': description: | Credit confirmation recorded. The response `submission.status` reflects the outcome: `CREDIT_CONFIRMED` (all findings credited), `FAILED` (all failed), or unchanged (`SUBMITTED` / `ACKNOWLEDGED`) on partial results. content: application/json: schema: $ref: '#/components/schemas/ShipClaimSubmissionResponse' '400': $ref: '#/components/responses/InvalidRequestError' '401': $ref: '#/components/responses/AuthError' '404': $ref: '#/components/responses/NotFoundError' '409': $ref: '#/components/responses/ActionNotAllowedError' '429': $ref: '#/components/responses/RateLimitedError' '500': $ref: '#/components/responses/InternalError' components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT integrationKeyHeader: type: apiKey in: header name: x-api-key parameters: IdempotencyKeyHeader: name: Idempotency-Key in: header required: false description: | Optional idempotency key for retry-safe enqueue semantics. Scoped by tenant + endpoint + key. schema: type: string minLength: 1 maxLength: 200 FindingIdPath: name: findingId in: path required: true schema: type: string format: uuid SubmissionIdPath: name: submissionId in: path required: true schema: type: string format: uuid responses: MissingApiKeyError: description: Missing integration key header content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' example: code: MISSING_API_KEY message: Integration key required in x-api-key header InvalidRequestError: description: Request validation failed content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' example: code: INVALID_REQUEST message: Invalid request payload AuthError: description: Authentication failed (invalid/revoked/expired token or key) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' examples: invalidToken: value: code: INVALID_TOKEN message: Invalid or expired token revokedKey: value: code: REVOKED_API_KEY message: This integration key has been revoked ScopeDeniedError: description: Principal authenticated but missing required scope(s) content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' example: code: SCOPE_DENIED message: Insufficient permissions details: required: ['ship:upload'] provided: ['jobs:read'] PrincipalDeniedError: description: Principal type is not allowed for this endpoint content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' example: code: PRINCIPAL_DENIED message: This endpoint requires integration authentication details: required: [integration] actual: clerk ActionNotAllowedError: description: Request conflicts with current resource/action state content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' example: code: ACTION_NOT_ALLOWED message: Idempotency-Key was already used with a different request payload RateLimitedError: description: Rate limit exceeded content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' example: code: RATE_LIMITED message: Rate limit exceeded details: bucket: read limit: 240 windowMs: 60000 retryAfterSeconds: 12 FeatureDisabledError: description: Feature is disabled by runtime flag headers: Retry-After: description: Seconds until retry is recommended schema: type: integer content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' example: code: FEATURE_DISABLED message: Self-serve signup is currently disabled details: retryAfterSeconds: 300 InternalError: description: Unexpected server error content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' example: code: INTERNAL_ERROR message: An unexpected error occurred NotFoundError: description: Requested resource not found content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' example: code: NOT_FOUND message: Resource not found DuplicateUploadError: description: Duplicate file upload rejected content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' example: code: DUPLICATE_UPLOAD message: This file has already been uploaded details: existingJobId: 11111111-1111-1111-1111-111111111111 uploadedAt: '2026-02-24T13:05:00.000Z' FileTooLargeError: description: File exceeds size limit content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' example: code: FILE_TOO_LARGE message: File too large. Maximum size is 50MB. InvalidFileFormatError: description: File type is not accepted content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' example: code: INVALID_FILE_FORMAT message: Only CSV files are allowed AdapterNotAllowedError: description: Integration key adapter allowlist denied upload content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' example: code: ADAPTER_NOT_ALLOWED message: Requested adapter is not allowed for this integration key schemas: ErrorEnvelope: type: object required: [code, message] properties: code: type: string message: type: string details: type: object additionalProperties: true IntegrationTokenResponse: type: object required: [access_token, token_type, expires_in] properties: access_token: type: string token_type: type: string enum: [Bearer] expires_in: type: integer example: 3600 default_adapter: type: string nullable: true PublicSignupSessionCreateRequest: type: object required: [companyName, modules, adminEmail, legalAcceptance] properties: companyName: type: string slug: type: string modules: type: array minItems: 1 items: type: string enum: [ship, trade] adminEmail: type: string format: email adminFirstName: type: string adminLastName: type: string planIntent: type: string enum: [sandbox, professional] legalAcceptance: type: object required: [termsVersion, privacyVersion, acceptedAt, ip] properties: termsVersion: type: string privacyVersion: type: string acceptedAt: type: string format: date-time ip: type: string userAgent: type: string PublicSignupSessionCreateResponse: type: object required: [sessionId, status, planIntent, verificationExpiresAt, verificationLastSentAt, expiresAt] properties: sessionId: type: string format: uuid status: type: string enum: [PENDING_EMAIL_VERIFICATION] planIntent: type: string enum: [sandbox, professional] verificationExpiresAt: type: string format: date-time verificationLastSentAt: type: string format: date-time expiresAt: type: string format: date-time PublicSignupSessionStatusResponse: type: object required: [sessionId, status] properties: sessionId: type: string format: uuid status: type: string enum: - PENDING_EMAIL_VERIFICATION - EMAIL_VERIFIED - PROVISIONING - PROVISIONED - REVIEW_REQUIRED - FAILED - EXPIRED PublicSignupProvisionRequest: type: object required: [idempotencyKey] properties: idempotencyKey: type: string PublicSignupProvisionResponse: type: object required: [sessionId, status, planIntent, tenantSlug, nextSteps, provisioningLeaseActive] properties: sessionId: type: string format: uuid status: type: string enum: - PROVISIONING - PROVISIONED - REVIEW_REQUIRED - FAILED planIntent: type: string enum: [sandbox, professional] tenantId: type: string format: uuid nullable: true tenantSlug: type: string clerkOrgId: type: string nullable: true signUpUrl: type: string nullable: true provisioningLeaseActive: type: boolean reviewReason: type: string nullable: true nextSteps: type: array items: type: string PublicSignupSessionGetResponse: type: object required: [sessionId, status, planIntent, companyName, slug, modules, expiresAt, createdAt, updatedAt] properties: sessionId: type: string format: uuid status: type: string planIntent: type: string enum: [sandbox, professional] companyName: type: string slug: type: string modules: type: array items: type: string reviewReason: type: string nullable: true failureReason: type: string nullable: true signUpUrl: type: string nullable: true tenantId: type: string format: uuid nullable: true verificationExpiresAt: type: string format: date-time nullable: true verificationLastSentAt: type: string format: date-time nullable: true provisioningLeaseActive: type: boolean firstRunCompletedAt: type: string format: date-time nullable: true expiresAt: type: string format: date-time createdAt: type: string format: date-time updatedAt: type: string format: date-time BillingSetupSessionRequest: type: object properties: billingEmail: type: string format: email description: Optional billing contact email stored on the tenant billing profile. BillingSetupSessionResponse: type: object required: [tenantId, setupIntentId, clientSecret, stripeCustomerId] properties: tenantId: type: string format: uuid setupIntentId: type: string clientSecret: type: string stripeCustomerId: type: string JobAcceptedResponse: type: object required: [jobId, status, message] properties: jobId: type: string format: uuid status: type: string enum: [PENDING, PROCESSING, COMPLETED, FAILED] message: type: string replayed: type: boolean description: True when the request replayed an existing job for the same idempotency key. TradeUploadRequest: type: object required: [file] properties: file: type: string format: binary runPolicy: type: string enum: [always, skip_exact_duplicates] default: always screeningAuthority: type: string enum: [CA, US] CatalogUploadRequest: type: object required: [file] properties: file: type: string format: binary sourceAdapter: type: string enum: [catalog-excel, extended-excel] default: catalog-excel screeningAuthority: type: string enum: [CA, US] UploadAcceptedResponse: type: object required: [jobId, status, message, fileName] properties: jobId: type: string format: uuid status: type: string enum: [PENDING, PROCESSING, COMPLETED, FAILED] message: type: string replayed: type: boolean description: True when the request replayed an existing job for the same idempotency key. fileName: type: string CatalogUploadAcceptedResponse: allOf: - $ref: '#/components/schemas/UploadAcceptedResponse' - type: object required: [sourceAdapter] properties: sourceAdapter: type: string enum: [catalog-excel, extended-excel] ShipUploadAcceptedResponse: type: object required: [jobId, fileName, status] properties: jobId: type: string format: uuid fileName: type: string status: type: string enum: [PENDING] OrderUploadAcceptedResponse: allOf: - $ref: '#/components/schemas/ShipUploadAcceptedResponse' - type: object required: [ingestMode] properties: ingestMode: type: string enum: [PATCH, SNAPSHOT] JobReconciliation: type: object required: [rowsUploaded, rowsSkipped, rowsDeduplicated, skusFound, skusScreened, skusNotApplicable] properties: rowsUploaded: type: integer nullable: true rowsSkipped: type: integer nullable: true rowsDeduplicated: type: integer nullable: true skusFound: type: integer skusScreened: type: integer skusNotApplicable: type: integer JobOutcomeCounts: type: object required: [atRisk, cleared, needsReview, falsePositive] properties: atRisk: type: integer cleared: type: integer needsReview: type: integer falsePositive: type: integer JobSkippedRows: type: object required: [counts] properties: counts: type: object nullable: true additionalProperties: type: integer sample: type: array nullable: true items: type: object additionalProperties: true JobResponse: type: object required: - id - type - status - progress - result - reconciliation - skippedRows - warnings - outcomes - error - createdAt - updatedAt properties: id: type: string format: uuid type: type: string status: type: string enum: [PENDING, PROCESSING, COMPLETED, FAILED] progress: type: integer fileName: type: string result: type: object additionalProperties: true reconciliation: $ref: '#/components/schemas/JobReconciliation' skippedRows: $ref: '#/components/schemas/JobSkippedRows' warnings: type: object nullable: true additionalProperties: type: integer outcomes: $ref: '#/components/schemas/JobOutcomeCounts' error: type: string nullable: true createdAt: type: string format: date-time updatedAt: type: string format: date-time JobsListResponse: type: object required: [jobs, count] properties: jobs: type: array items: $ref: '#/components/schemas/JobResponse' count: type: integer SimaBatchRequest: type: object properties: skus: type: array nullable: true items: type: string runPolicy: type: string enum: [always, skip_exact_duplicates] default: always screeningAuthority: type: string enum: [CA, US] SimaResultItem: type: object required: - id - sku - outcome - status - workflowStatus - hsInScope - currentHs - confidence - analysisDate - isStale - exposureAnnual - missingAttributes properties: id: type: string format: cuid sku: type: string outcome: type: string enum: [AT_RISK, NEEDS_REVIEW, CLEARED] status: type: string enum: [AT_RISK, NEEDS_REVIEW, CLEARED] workflowStatus: type: string nullable: true enum: [OPEN, OVERRIDDEN, ACCEPTED, DISMISSED] reasonCode: type: string nullable: true measureCode: type: string nullable: true hsInScope: type: boolean currentHs: type: string correctedHs: type: string nullable: true countryOfOrigin: type: string nullable: true materialType: type: string nullable: true confidence: type: number evidence: type: object nullable: true additionalProperties: true analysisDate: type: string format: date-time simaConfigVersion: type: string nullable: true needsReviewSince: type: string format: date-time nullable: true isStale: type: boolean exposureAnnual: type: number nullable: true adRate: type: number nullable: true cvdRate: type: number nullable: true combinedRate: type: number nullable: true caseNumber: type: string nullable: true hasRestrictedComponents: type: boolean missingAttributes: type: array items: type: string SimaResultsResponse: type: object required: [outcomes, total, limit, hasMore, cursor] properties: outcomes: type: array items: $ref: '#/components/schemas/SimaResultItem' total: type: integer limit: type: integer offset: type: integer cursor: type: string nullable: true hasMore: type: boolean SimaSummaryResponse: type: object required: [totalSkus, atRisk, needsReview, totalExposure] properties: totalSkus: type: integer atRisk: type: integer needsReview: type: integer totalExposure: type: number SimaEvidenceResponse: type: object required: [result, classification, attributes, measureApplicability, thresholds, exposure, history, auditTrail] properties: result: type: object additionalProperties: true classification: type: object additionalProperties: true attributes: type: object additionalProperties: true measureApplicability: type: object additionalProperties: true thresholds: type: object additionalProperties: true exposure: type: object additionalProperties: true history: type: array items: type: object additionalProperties: true auditTrail: type: array items: type: object additionalProperties: true SimaReportJsonResponse: type: object required: [generatedAt, tenantId, configVersion] properties: generatedAt: type: string format: date-time tenantId: type: string format: uuid configVersion: type: string totalOutcomes: type: integer totalExposures: type: integer outcomes: type: array items: type: object additionalProperties: true exposures: type: array items: type: object additionalProperties: true TradeFeedUploadRequest: type: object required: [file, originCountry, defaultDestinationCountry] properties: file: type: string format: binary originCountry: type: string description: Default origin country code (ISO 3166-1 alpha-2) when rows omit it. Required. defaultDestinationCountry: type: string description: Default destination country code (ISO 3166-1 alpha-2) when rows omit it. Required. screeningAuthority: type: string enum: [CA, US] TradeFeedSessionStatus: type: string enum: [UPLOADED, NORMALIZING, READY, PREVIEW_BLOCKED, FAILED, APPLIED] TradeFeedUploadAcceptedResponse: type: object required: [sessionId, jobId, status, fileName] description: | Envelope returned by `POST /api/trade/feeds/upload` with HTTP 202. The response represents a feed session that has been enqueued for normalization — it is not the full session detail. Poll `GET /api/trade/feeds/{sessionId}` for normalization progress and the full `TradeFeedSessionResponse` payload. properties: sessionId: type: string format: uuid description: ID of the durable feed session that was created (or replayed). jobId: type: string format: uuid description: ID of the background normalization job for this session. Poll `GET /api/jobs/{jobId}` if job-level progress is needed. status: $ref: '#/components/schemas/TradeFeedSessionStatus' description: Initial session status at the moment of acceptance (typically `NORMALIZING`). fileName: type: string description: Original filename as uploaded, echoed back for client-side correlation. replayed: type: boolean description: True when the request replayed an existing session for the same `Idempotency-Key` + payload. Absent or false on first-write. TradeFeedSessionResponse: type: object description: > Full session status response from GET /api/trade/feeds/:sessionId. Field names match the runtime buildTradeFeedSessionResponse() output. required: [id, status, version] properties: id: type: string format: uuid description: Session ID (note: field is `id`, not `sessionId`). status: $ref: '#/components/schemas/TradeFeedSessionStatus' version: type: integer description: Optimistic concurrency version for the session. sourceFileName: type: string sourceContentType: type: string profile: type: string enum: [attribute_row, sku_row] nullable: true defaults: type: object properties: originCountry: type: string defaultDestinationCountry: type: string screeningAuthority: type: string enum: [CA, US] summary: type: object description: Normalization summary (shape varies by session state). nullable: true totalRows: type: integer description: Total CSV rows parsed (was `rowCount` in earlier spec versions). normalizedSkuCount: type: integer nullable: true issueCount: type: integer description: Total issues (blocking + non-blocking). Use this to gate operator handoff. blockingIssueCount: type: integer description: Blocking issues that prevent apply. previewable: type: boolean description: Whether classify-preview can be enqueued for this session. applyable: type: boolean description: Whether direct apply is available. Check this + issueCount to determine the PREVIEW_BLOCKED shortcut path. latestNormalizeJobId: type: string format: uuid nullable: true latestPreviewJobId: type: string format: uuid nullable: true latestNormalizeJob: type: object nullable: true description: Embedded normalize job status (id, status, progress, type, createdAt, updatedAt). latestPreviewJob: type: object nullable: true description: Embedded preview job status (id, status, progress, type, updatedAt). normalizeRetryCount: type: integer description: Number of normalize retries attempted (0 = no retries). maxNormalizeRetries: type: integer description: Maximum normalize retries allowed by the system. createdAt: type: string format: date-time updatedAt: type: string format: date-time TradeFeedSkuItem: type: object required: [skuKey, sku] properties: skuKey: type: string sku: type: string productName: type: string nullable: true originCountry: type: string nullable: true destinationCountry: type: string nullable: true providedHs: type: string nullable: true blockingIssueKeys: type: array items: type: string warningIssueKeys: type: array items: type: string TradeFeedSkuListResponse: type: object required: [items, total] properties: items: type: array items: $ref: '#/components/schemas/TradeFeedSkuItem' total: type: integer limit: type: integer offset: type: integer TradeFeedIssueItem: type: object required: [issueKey, skuKey, issueType, isBlocking, message] properties: issueKey: type: string skuKey: type: string issueType: type: string isBlocking: type: boolean message: type: string acknowledgedAt: type: string format: date-time nullable: true TradeFeedIssueListResponse: type: object required: [items, total] properties: items: type: array items: $ref: '#/components/schemas/TradeFeedIssueItem' total: type: integer limit: type: integer offset: type: integer TradeFeedPreviewEnqueueResponse: type: object required: [jobId, status] properties: jobId: type: string format: uuid previewJobId: type: string format: uuid status: type: string enum: [PENDING, PROCESSING, COMPLETED, FAILED] message: type: string TradeClassifyPreviewReviewRequest: type: object required: [rows] properties: rows: type: array items: type: object additionalProperties: true toggles: type: object additionalProperties: true TradeClassifyPreviewReviewResponse: type: object required: [jobId, reviewedAt] properties: jobId: type: string format: uuid reviewedAt: type: string format: date-time staleOnApply: type: boolean ShipActionabilityCounts: type: object properties: disputeReady: type: integer reviewRequired: type: integer blocked: type: integer ShipAuditRow: type: object description: Compact shipment row for the audit list, including narrative summary fields. properties: id: type: string format: uuid trackingNumber: type: string carrier: type: string shipDate: type: string format: date-time rollupOutcome: type: string enum: [AT_RISK, CLEAR, NEEDS_REVIEW] nullable: true hasActiveFindings: type: boolean totalVariance: type: number nullable: true primaryFindingId: type: string format: uuid nullable: true description: ID of the highest-ranked active finding on this shipment. primaryActionability: type: string enum: [DISPUTE_READY, REVIEW_REQUIRED, BLOCKED] nullable: true description: Actionability of the primary finding. Null if no active findings. primaryHeadline: type: string nullable: true description: Headline of the primary finding. pricingBlockerSummary: type: string nullable: true description: Human-readable summary of pricing blockers from the latest rate-engine calculation. pricingBlockers: type: array items: type: object properties: code: type: string headline: type: string detail: type: string description: Pricing blockers derived from the latest ShipmentCalculation. actionabilityCounts: $ref: '#/components/schemas/ShipActionabilityCounts' ShipShipmentDetail: type: object description: Full shipment detail including weight/dimension fields and explanation summary. properties: id: type: string format: uuid trackingNumber: type: string carrier: type: string serviceLevel: type: string nullable: true shipDate: type: string format: date-time invoiceNumber: type: string nullable: true invoiceDate: type: string format: date-time nullable: true billedAmount: type: number nullable: true expectedAmount: type: number nullable: true actualWeight: type: number nullable: true billedWeight: type: number nullable: true primaryFindingId: type: string format: uuid nullable: true primaryActionability: type: string enum: [DISPUTE_READY, REVIEW_REQUIRED, BLOCKED] nullable: true primaryHeadline: type: string nullable: true pricingBlockerSummary: type: string nullable: true pricingBlockers: type: array items: type: object properties: code: type: string headline: type: string detail: type: string actionabilityCounts: $ref: '#/components/schemas/ShipActionabilityCounts' rollupOutcome: type: string enum: [AT_RISK, CLEAR, NEEDS_REVIEW] nullable: true ShipFindingItem: type: object required: [id, workflowStatus] properties: id: type: string format: uuid shipmentId: type: string format: uuid nullable: true workflowStatus: type: string enum: [OPEN, DISPUTED, SUBMITTED, CARRIER_REVIEW, CREDITED, REJECTED, DISMISSED] findingType: type: string nullable: true amount: type: number nullable: true currency: type: string nullable: true disputedAt: type: string format: date-time nullable: true resolvedAt: type: string format: date-time nullable: true createdAt: type: string format: date-time updatedAt: type: string format: date-time actionability: type: string enum: [DISPUTE_READY, REVIEW_REQUIRED, BLOCKED] description: Whether the finding is dispute-ready, needs manual review, or is blocked on missing data. headline: type: string description: Short operator-facing summary of the finding. allowedActions: type: array items: type: string enum: [dispute, dismiss, submit, carrier-review, credit, reject, reopen] description: Workflow actions available for this finding given its current state and actionability. claimEligibility: type: string enum: [ELIGIBLE, INELIGIBLE] description: Whether the finding can be bundled into a new claim submission. claimBlockerReason: type: string nullable: true enum: [must_dispute_first, workflow_resolved, carrier_blocked, already_in_active_submission] description: If ineligible, the specific reason. Null when eligible. explanationExactness: type: string nullable: true enum: [EXACT_COMPONENT, TOTAL_ONLY, ASSUMPTION_BEARING, UNAVAILABLE] description: >- Classification of how precise the component comparison is. Only present on AMOUNT_VARIANCE findings in shipment detail views; omitted entirely for other finding types. Value is UNAVAILABLE when no rate-engine calculation exists (discrepancyExplanation will be null). discrepancyExplanation: nullable: true description: >- Component-level breakdown of billed vs expected variance. Only present on AMOUNT_VARIANCE findings in shipment detail views; omitted entirely for other finding types. Null within AMOUNT_VARIANCE scope when exactness is UNAVAILABLE (no rate-engine calculation). allOf: - $ref: '#/components/schemas/DiscrepancyExplanation' DiscrepancyExplanation: type: object properties: exactness: type: string enum: [EXACT_COMPONENT, TOTAL_ONLY, ASSUMPTION_BEARING, UNAVAILABLE] components: type: array items: type: object properties: component: type: string enum: [transportation, fuel, accessorials] billedAmount: type: number expectedAmount: type: number delta: type: number primaryDriver: type: string nullable: true enum: [transportation, fuel, accessorials] description: Component with the largest absolute delta. billedBreakdown: type: object properties: transportation: type: number fuel: type: number accessorials: type: number other: type: number expectedMetadata: type: object nullable: true properties: zone: type: string nullable: true billableWeight: type: number nullable: true method: type: string nullable: true fuelPercent: type: string nullable: true fuelEstimated: type: boolean contractVersionId: type: string format: uuid nullable: true caveats: type: array items: type: string ShipFindingResponse: type: object required: [finding] properties: finding: $ref: '#/components/schemas/ShipFindingItem' ShipFindingListResponse: type: object required: [items, total] properties: items: type: array items: $ref: '#/components/schemas/ShipFindingItem' total: type: integer limit: type: integer offset: type: integer statusCounts: type: object additionalProperties: type: integer ShipFindingBatchRequest: type: object required: [action, findingIds] properties: action: type: string enum: [dispute, dismiss, submit, carrier-review, reject, reopen] description: | Batch credit is not permitted (`action=credit` returns `400 INVALID_REQUEST`). Use the per-finding credit endpoint. findingIds: type: array minItems: 1 items: type: string format: uuid ShipFindingBatchResponse: type: object required: [results] properties: results: type: array items: type: object required: [findingId, status] properties: findingId: type: string format: uuid status: type: string enum: [ok, skipped, invalid_transition, not_found] workflowStatus: type: string nullable: true ShipFindingCreditRequest: type: object required: [amount, confirmation] properties: amount: type: number exclusiveMinimum: 0 confirmation: type: object required: [source, referenceId, confirmedAt] properties: source: type: string referenceId: type: string confirmedAt: type: string format: date-time notes: type: string artifactUrl: type: string ShipClaimSubmissionStatus: type: string enum: [DRAFT, READY, SUBMITTED, ACKNOWLEDGED, CREDIT_CONFIRMED, CLOSED, FAILED] ShipClaimSubmission: type: object description: Bare claim submission record. Responses wrap this inside the `ShipClaimSubmissionResponse` envelope. required: [id, status] properties: id: type: string format: uuid status: $ref: '#/components/schemas/ShipClaimSubmissionStatus' carrier: type: string nullable: true findingIds: type: array items: type: string format: uuid packetUrl: type: string nullable: true submittedAt: type: string format: date-time nullable: true acknowledgedAt: type: string format: date-time nullable: true creditConfirmedAt: type: string format: date-time nullable: true externalReference: type: string nullable: true description: Carrier-facing reference string captured on submit/acknowledge. createdAt: type: string format: date-time updatedAt: type: string format: date-time ShipClaimSubmissionResponse: type: object description: Envelope returned by all claim submission mutations (create, generate-packet, submit, acknowledge, confirm-credit) and by the single-submission GET. The submission object is always nested under `submission`, never returned bare. required: [submission] properties: submission: $ref: '#/components/schemas/ShipClaimSubmission' ShipClaimSubmissionListResponse: type: object description: Envelope returned by `GET /api/ship/claims/submissions`. List items live under `submissions`, not `items`. required: [submissions, total] properties: submissions: type: array items: $ref: '#/components/schemas/ShipClaimSubmission' total: type: integer limit: type: integer offset: type: integer hasMore: type: boolean ShipClaimSubmissionReferenceBody: type: object description: Request body required by `POST /api/ship/claims/submissions/{submissionId}/submit` and `.../acknowledge`. Omitting `externalReference` or sending an empty string returns `400 INVALID_REQUEST`. required: [externalReference] properties: externalReference: type: string minLength: 1 description: Non-empty carrier-facing reference string recorded on the submission (for example, the carrier's claim ticket ID or the customer's internal dispute reference). ShipClaimSubmissionCreateRequest: type: object required: [carrier, findingIds] properties: carrier: type: string findingIds: type: array minItems: 1 items: type: string format: uuid notes: type: string ShipClaimSubmissionConfirmCreditRequest: type: object required: [confirmation] properties: confirmation: type: object required: [source, referenceId, confirmedAt] description: Credit confirmation details from the carrier. properties: source: type: string description: Source of the credit confirmation (e.g. carrier portal, email). referenceId: type: string description: Carrier-provided credit reference identifier. confirmedAt: type: string format: date-time description: Timestamp when the carrier confirmed the credit. notes: type: string description: Optional free-text notes about the credit confirmation. artifactUrl: type: string description: Optional URL to a supporting artifact (e.g. carrier credit memo). amountsByFinding: type: object description: Optional per-finding credit amounts keyed by finding ID. When provided, each finding in the submission receives its specified credit amount rather than a blanket confirmation. additionalProperties: type: number reason: type: string description: Optional reason or note for the credit confirmation action.