Skip to Content
APISHIP Claim Submission Contract

SHIP Claim Submission Workflow Contract

Status: Active Version: v1 Last updated: 2026-04-08

This document is the normative contract for the SHIP claim submission workflow. Claim submissions have a distinct state machine from findings and are documented separately from docs/api/ship-finding-contract-v1.md — a submission aggregates one or more findings into a packet that can be sent to a carrier, and its state transitions independently of the underlying findings’ workflow states.

Routes in scope

All routes below are callable with an integration-key Bearer JWT obtained via POST /api/auth/token/integration.

MethodRoutePurpose
POST/api/ship/claims/submissionsCreate a new submission in DRAFT
GET/api/ship/claims/submissionsList submissions with optional filters
GET/api/ship/claims/submissions/:submissionIdGet submission detail
POST/api/ship/claims/submissions/:submissionId/generate-packetGenerate the claim packet; DRAFT → READY
POST/api/ship/claims/submissions/:submissionId/submitSubmit to carrier; READY → SUBMITTED
POST/api/ship/claims/submissions/:submissionId/acknowledgeRecord carrier receipt; SUBMITTED → ACKNOWLEDGED
POST/api/ship/claims/submissions/:submissionId/confirm-creditRecord carrier credit confirmation; callable from SUBMITTED, ACKNOWLEDGED, or FAILED

State machine

DRAFT ──generate-packet──▶ READY ──submit──▶ SUBMITTED ──acknowledge──▶ ACKNOWLEDGED │ │ │ │ │ │ │ │ │ ▼ │ │ ├──confirm-credit──▶ CREDIT_CONFIRMED │ │ │ │ │ │ │ │ │ ▼ └──────────────────────── (failure at any step can transition to FAILED) CLOSED │ ▲ │ (operator close) confirm-credit (retry)

confirm-credit is callable from three states: SUBMITTED, ACKNOWLEDGED, and FAILED. The outcome depends on per-finding credit confirmation results (see Confirm-credit outcomes below).

State definitions:

  • DRAFT — submission created but packet not yet generated. Findings can still be added or removed by re-creating the submission (creation is the add path; modify by delete-and-recreate is the simplest pattern).
  • READY — claim packet has been generated and is ready to submit. packetUrl is available.
  • SUBMITTED — packet has been sent to the carrier. submittedAt is set. confirm-credit is callable from this state.
  • ACKNOWLEDGED — the carrier confirmed receipt. acknowledgedAt is set. confirm-credit is callable from this state.
  • CREDIT_CONFIRMED — carrier credited the claim. creditConfirmedAt is set. Credit confirmation on a submission is separate from per-finding CREDITED — the submission-level credit groups the contested amount at the carrier level while the per-finding credit records the billing-critical individual credit (see ship-finding-contract-v1.md).
  • CLOSED — terminal administrative state; submission will not be reopened.
  • FAILED — failure at packet generation, submission, or credit confirmation. confirm-credit is callable from this state as a retry path. Inspect the submission detail for the reason.

The canonical state set is defined by the ShipClaimSubmissionStatus enum in apps/api/prisma/schema.prisma and enforced by apps/api/src/lib/ship/claim-submissions.ts.

Relationship to the finding workflow

  • A submission is created from a non-empty set of findingIds. Each finding must pass the claim eligibility check (see ship-finding-contract-v1.md, “Claim eligibility matrix”):
    • Only findings with claimEligibility: ELIGIBLE can be bundled. Ineligible findings are rejected with 400 INVALID_REQUEST and a invalidFindingStates detail array naming each finding + its claimBlockerReason.
    • Eligible workflow states: DISPUTED, SUBMITTED, CARRIER_REVIEW.
    • Ineligible states: OPEN (must dispute first), DISMISSED/CREDITED/REJECTED (workflow resolved), carrier-blocked findings, and findings already linked to an active (non-terminal) claim submission.
    • A finding already linked to an active submission (status not in CREDIT_CONFIRMED/CLOSED/FAILED) returns 409 ACTION_NOT_ALLOWED with a duplicateLinks detail array naming the conflicting submission.
  • Creating a submission does not automatically transition the underlying findings. The finding workflow and the submission workflow are orthogonal — a caller that wants an end-to-end “dispute and submit” flow must call the finding dispute/submit endpoints separately.
  • Per-finding CREDITED transitions are still the billing-critical writes. A submission-level CREDIT_CONFIRMED does not replace the per-finding credit endpoint — it records the outer carrier response, while the per-finding credit endpoint is the billing-input-of-record.
  • Each finding in a submission response includes the same explanation fields (actionability, headline, claimEligibility, claimBlockerReason, etc.) as described in ship-finding-contract-v1.md.

Happy path

  1. POST /api/ship/claims/submissions with { carrier, findingIds[], notes? }. Response is the submission in DRAFT, wrapped in the { submission } envelope described below.
  2. POST /api/ship/claims/submissions/:submissionId/generate-packet. Response envelope includes submission.packetUrl and submission.status === 'READY'.
  3. POST /api/ship/claims/submissions/:submissionId/submit with { "externalReference": "..." }. externalReference is required and must be a non-empty string (a carrier-facing reference, e.g. the carrier’s claim ticket ID). Omitting it or sending an empty string returns 400 INVALID_REQUEST. Status becomes SUBMITTED.
  4. When the carrier confirms receipt: POST /api/ship/claims/submissions/:submissionId/acknowledge with { "externalReference": "..." }. externalReference is required on acknowledge too (a non-empty string; omission returns 400 INVALID_REQUEST). Status becomes ACKNOWLEDGED.
  5. When the carrier credits the claim: POST /api/ship/claims/submissions/:submissionId/confirm-credit with { confirmation: { source, referenceId, confirmedAt, notes?, artifactUrl? }, amountsByFinding?, reason? }. The resulting submission.status depends on per-finding outcomes (see Confirm-credit outcomes).

After CREDIT_CONFIRMED, record the billing-critical per-finding credits via POST /api/ship/findings/:findingId/credit for each finding in the submission (see ship-finding-contract-v1.md).

Confirm-credit outcomes

confirm-credit is callable from SUBMITTED, ACKNOWLEDGED, or FAILED. The handler attempts to confirm credit for each finding linked to the submission. The resulting submission status depends on the per-finding results:

Per-finding resultSubmission outcomeNotes
All findings credited successfullyCREDIT_CONFIRMEDcreditConfirmedAt is set. Terminal success.
All findings failedFAILEDfailedAt and failureReason are set. Caller may retry by calling confirm-credit again.
Partial (some succeed, some fail)Stays at current statusIf called from SUBMITTED, stays SUBMITTED. If called from ACKNOWLEDGED or FAILED, stays ACKNOWLEDGED.

Retry path: Because FAILED is a valid entry state for confirm-credit, callers can retry after a previous failure without resetting the submission. This is the intended recovery mechanism for transient credit-confirmation failures.

Response envelope

Every claim submission mutation (POST /api/ship/claims/submissions, .../generate-packet, .../submit, .../acknowledge, .../confirm-credit) and the single-submission detail GET return the submission nested under a submission key — never as a bare object:

{ "submission": { "id": "7c1e3e2c-22d4-4b79-9d1a-4a8b3f5d6c77", "status": "SUBMITTED", "carrier": "demo-carrier", "findingIds": ["3b1b5c4c-...-..."], "packetUrl": "https://...", "submittedAt": "2026-04-08T17:03:22.511Z", "acknowledgedAt": null, "creditConfirmedAt": null, "externalReference": "DEMO-REF-0001", "createdAt": "2026-04-08T17:02:10.100Z", "updatedAt": "2026-04-08T17:03:22.511Z" } }

The list endpoint GET /api/ship/claims/submissions uses a distinct envelope keyed on submissions (plural): { submissions: [...], total, limit, offset, hasMore }. It does not use an items key.

Example submit call:

curl -X POST \ "https://api.rgl8r.com/api/ship/claims/submissions/$SUBMISSION_ID/submit" \ -H "Authorization: Bearer $BEARER_JWT" \ -H "Content-Type: application/json" \ -d '{"externalReference":"DEMO-REF-0001"}'

Example acknowledge call:

curl -X POST \ "https://api.rgl8r.com/api/ship/claims/submissions/$SUBMISSION_ID/acknowledge" \ -H "Authorization: Bearer $BEARER_JWT" \ -H "Content-Type: application/json" \ -d '{"externalReference":"DEMO-REF-0001-ACK"}'

Error envelopes

All errors follow the canonical public envelope. Endpoint-specific errors:

CodeHTTPWhen
INVALID_REQUEST400Missing carrier/findingIds on create, missing or empty externalReference on submit/acknowledge, invalid confirmation shape on confirm-credit
INVALID_TOKEN401JWT expired or revoked
NOT_FOUND404Submission does not belong to the tenant, or referenced findings not found
ACTION_NOT_ALLOWED409Transition not allowed from current state, carrier policy blocks the action, or findings are in an invalid combination of states
RATE_LIMITED429Write bucket
INTERNAL_ERROR500Unexpected failure

Common 409 subcodes surfaced in details:

  • carrier_blocked — the tenant’s carrier policy (e.g. manual-import-only) blocks the action.
  • invalid_transition — current state is not a valid source for the requested transition.
  • invalid_finding_state — one or more findings are not eligible to bundle into a claim.
  • missing_findingsfindingIds list is empty after filtering to the tenant.

Retry guidance

  • Create submission (POST /api/ship/claims/submissions) is not idempotent — retries may create duplicate submissions. Prefer to poll by findingIds first if the caller cannot remember whether the previous attempt succeeded.
  • Transition calls (generate-packet, submit, acknowledge) are safe to retry when the previous attempt left the submission in an intermediate state; a retry into the same state returns ACTION_NOT_ALLOWED which the caller should treat as success.
  • confirm-credit is explicitly callable from FAILED as a retry path. If the previous attempt transitioned to FAILED, calling confirm-credit again will re-attempt per-finding credit confirmation. If it already reached CREDIT_CONFIRMED, a retry returns ACTION_NOT_ALLOWED.
  • Retry on 429 using details.retryAfterSeconds; do not retry ACTION_NOT_ALLOWED without re-reading state.

Cross-references

  • OpenAPI artifact: docs/api/openapi/rgl8r-public-api-v1.2.0.yaml
  • Public API contract pack: docs/api/public-api-contract-v1.md
  • Finding workflow contract (upstream of submissions): docs/api/ship-finding-contract-v1.md
  • Quickstart and agent kit: docs/developers/quickstart.md, docs/api/agent-integration-kit-v1.md
  • Sample fixture: docs/api/examples/fixtures/ship-invoice-sample.csv
  • Example scripts: docs/api/examples/rgl8r_ship_quickstart.ts, docs/api/examples/rgl8r_ship_quickstart.py