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.