openapi: 3.1.0
info:
  title: Carrier-Connect Partner API
  version: 1.1.0
  description: |
    The Carrier-Connect Partner API lets TMS platforms and brokers post loads to the
    Carrier-Connect load board, receive carrier offers, award loads, manage a
    Do-Not-Use (DNU) carrier list, and check carrier verification status.

    Carrier-Connect is a load board and does not process freight payments. Prices sent
    through this API are informational; payment terms are handled between the parties.

    **Authentication.** Send your API key as a Bearer token:
    `Authorization: Bearer cc_live_...`. Keys are issued by Carrier-Connect.

    **Environments.** The base URL is the same for both. Keys starting with `cc_test_`
    use the sandbox: responses have realistic shapes but nothing touches real loads or
    carriers. Keys starting with `cc_live_` use production.

    **Scopes.** Each key carries scopes: `loads:read`, `loads:write`, `loads:cancel`,
    `loads:assign`, `carriers:read`. A request without the required scope gets `403`.

    **Rate limits.** 60 requests per minute and 10,000 per day per key by default.
    Exceeding them returns `429` with a `Retry-After` header (seconds).

    **Errors.** Every error has the shape `{ "error": { "message": "..." } }`.
  contact:
    name: Carrier-Connect Partner Support
    url: https://www.carrier-connect.center/developers
servers:
  - url: https://www.carrier-connect.center/api/v1
security:
  - bearerAuth: []

tags:
  - name: Loads
  - name: Offers & awards
  - name: Do-Not-Use list
  - name: Carriers

paths:
  /loads:
    get:
      tags: [Loads]
      summary: List your loads
      description: Loads posted by the account that owns this key, newest first. Scope `loads:read`.
      operationId: listLoads
      responses:
        '200':
          description: Your loads.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: '#/components/schemas/Load' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }
        '429': { $ref: '#/components/responses/RateLimited' }
    post:
      tags: [Loads]
      summary: Post a load
      description: |
        Publishes a load on the board immediately. Scope `loads:write`. Only broker
        accounts with an active membership can post loads.
      operationId: createLoad
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CreateLoadInput' }
      responses:
        '201':
          description: Load published.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Load' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403':
          description: Missing scope, not a broker account, or no active membership.
          content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
        '422': { $ref: '#/components/responses/Unprocessable' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /loads/{id}:
    parameters:
      - $ref: '#/components/parameters/LoadId'
    get:
      tags: [Loads]
      summary: Get a load
      description: Scope `loads:read`. Returns `404` if the load doesn't exist or isn't yours.
      operationId: getLoad
      responses:
        '200':
          description: The load.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Load' }
        '404': { $ref: '#/components/responses/NotFound' }
    patch:
      tags: [Loads]
      summary: Update a load
      description: |
        Scope `loads:write`. Send only the fields that change. Allowed while the load is
        `draft`, `published`, `offer_received` or `carrier_selected`; otherwise `409`.
      operationId: updateLoad
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/UpdateLoadInput' }
      responses:
        '200':
          description: Updated load.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Load' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }

  /loads/{id}/cancel:
    parameters:
      - $ref: '#/components/parameters/LoadId'
    post:
      tags: [Loads]
      summary: Cancel a load
      description: |
        Scope `loads:cancel`. Withdraws the load from the board (status `withdrawn`).
        Only possible before a carrier is assigned; otherwise `409`.
      operationId: cancelLoad
      responses:
        '200':
          description: Load withdrawn.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Load' }
        '404': { $ref: '#/components/responses/NotFound' }
        '409': { $ref: '#/components/responses/Conflict' }

  /loads/{id}/offers:
    parameters:
      - $ref: '#/components/parameters/LoadId'
    get:
      tags: [Offers & awards]
      summary: List offers on a load
      description: |
        Scope `loads:read`. Offers from Carrier-Connect carriers, newest first, each with
        the carrier's identity and overall verification status. Carriers on your DNU list
        cannot send offers to your loads.
      operationId: listOffers
      responses:
        '200':
          description: Offers on this load.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: '#/components/schemas/Offer' } }
        '404': { $ref: '#/components/responses/NotFound' }

  /loads/{id}/assign:
    parameters:
      - $ref: '#/components/parameters/LoadId'
    post:
      tags: [Offers & awards]
      summary: Award a load to a carrier
      description: |
        Scope `loads:assign`. Assigns the load to a carrier and generates the Rate
        Confirmation in the same step. Pending offers from other carriers are kept
        until the carrier signs the Rate Confirmation, so the load can reopen quickly
        if you reject the award.
      operationId: assignLoad
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/AssignLoadInput' }
      responses:
        '200':
          description: Load awarded; returns the Rate Confirmation.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/RateConfirmation' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /loads/{id}/reject:
    parameters:
      - $ref: '#/components/parameters/LoadId'
    post:
      tags: [Offers & awards]
      summary: Reject an award
      description: |
        Scope `loads:assign`. Reverses an award (for example, the carrier failed your
        compliance checks). The assignment and Rate Confirmation are voided and the load
        reopens immediately for other carriers, using offers that are still valid.
      operationId: rejectAward
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [reasonCode]
              properties:
                reasonCode: { $ref: '#/components/schemas/RejectReasonCode' }
      responses:
        '200':
          description: Award rejected; returns the reopened load.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Load' }
        '409': { $ref: '#/components/responses/Conflict' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /dnu:
    get:
      tags: [Do-Not-Use list]
      summary: Get your DNU list
      description: Scope `loads:read`. Carriers blocked from sending offers to your loads.
      operationId: listDnu
      responses:
        '200':
          description: Your DNU list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: '#/components/schemas/DnuEntry' } }
    put:
      tags: [Do-Not-Use list]
      summary: Replace your DNU list
      description: Scope `loads:write`, broker accounts only. Replaces the whole list (full sync).
      operationId: replaceDnu
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [carriers]
              properties:
                carriers: { type: array, items: { $ref: '#/components/schemas/DnuEntryInput' } }
      responses:
        '200':
          description: The new list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: '#/components/schemas/DnuEntry' } }
        '403': { $ref: '#/components/responses/Forbidden' }
        '422': { $ref: '#/components/responses/Unprocessable' }
    patch:
      tags: [Do-Not-Use list]
      summary: Add or remove DNU entries
      description: Scope `loads:write`, broker accounts only. Incremental changes without resending the whole list.
      operationId: updateDnu
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                add: { type: array, items: { $ref: '#/components/schemas/DnuEntryInput' } }
                remove: { type: array, items: { $ref: '#/components/schemas/DnuEntryInput' } }
      responses:
        '200':
          description: The updated list.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: '#/components/schemas/DnuEntry' } }
        '403': { $ref: '#/components/responses/Forbidden' }
        '422': { $ref: '#/components/responses/Unprocessable' }

  /carriers:
    get:
      tags: [Carriers]
      summary: Look up a carrier by USDOT or MC
      description: |
        Scope `carriers:read`. Exact match on USDOT and/or MC number (with or without an
        "MC" prefix). Returns carriers registered on the Carrier-Connect load board with
        each verification layer. Contact details are never included.
      operationId: lookupCarrier
      parameters:
        - name: usdot
          in: query
          schema: { type: string, example: '1234567' }
        - name: mc
          in: query
          schema: { type: string, example: '765432' }
      responses:
        '200':
          description: Matching carriers (empty array if none).
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { type: array, items: { $ref: '#/components/schemas/Carrier' } }
        '422':
          description: Neither usdot nor mc was provided.
          content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }

  /carriers/{id}:
    get:
      tags: [Carriers]
      summary: Get a carrier's verification
      description: Scope `carriers:read`. Use the `carrier_id` you receive in offers.
      operationId: getCarrier
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
      responses:
        '200':
          description: The carrier.
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Carrier' }
        '404': { $ref: '#/components/responses/NotFound' }

webhooks:
  loadEvent:
    post:
      summary: Event delivered to your webhook URL
      description: |
        Carrier-Connect sends a `POST` with a JSON body to the URL registered for your
        account. Events: `load.created`, `load.updated`, `load.covered`, `load.reopened`,
        `load.cancelled`, `load.award_rejected` (body: the Load) and `load_offer.created` (body: the Offer
        with its carrier).

        Headers on every delivery:
        - `x-carrier-connect-event`: the event type.
        - `x-carrier-connect-delivery`: unique delivery id, identical across retries. Use it to ignore duplicates.
        - `x-carrier-connect-timestamp`: Unix time (seconds) when this attempt was sent.
        - `x-carrier-connect-signature-v2`: hex HMAC-SHA256 of `<timestamp>.<raw body>` with your signing secret.
        - `x-carrier-connect-signature` (deprecated): hex HMAC-SHA256 of the raw body only.

        Verify `x-carrier-connect-signature-v2` against the raw body and reject deliveries
        whose timestamp is more than 5 minutes old. Respond with any `2xx` within 8
        seconds. Failed deliveries are retried up to 7 times over about 24 hours.
        Redirects are not followed.
      parameters:
        - { name: x-carrier-connect-event, in: header, required: true, schema: { $ref: '#/components/schemas/EventType' } }
        - { name: x-carrier-connect-delivery, in: header, required: true, schema: { type: string, format: uuid } }
        - { name: x-carrier-connect-timestamp, in: header, required: true, schema: { type: string, example: '1790451200' } }
        - { name: x-carrier-connect-signature-v2, in: header, required: true, schema: { type: string } }
        - { name: x-carrier-connect-signature, in: header, deprecated: true, schema: { type: string } }
      requestBody:
        content:
          application/json:
            schema:
              oneOf:
                - $ref: '#/components/schemas/Load'
                - $ref: '#/components/schemas/Offer'
      responses:
        '200':
          description: Received.

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: 'API key: `cc_live_...` (production) or `cc_test_...` (sandbox).'

  parameters:
    LoadId:
      name: id
      in: path
      required: true
      schema: { type: string, format: uuid }

  responses:
    BadRequest:
      description: Invalid JSON body.
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    Unauthorized:
      description: Missing, invalid, expired or revoked API key.
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    Forbidden:
      description: The key lacks the required scope, or the account type can't perform this action.
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    NotFound:
      description: Not found, or not owned by your account.
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    Conflict:
      description: The load's current status doesn't allow this action.
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    Unprocessable:
      description: Missing or invalid fields.
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
    RateLimited:
      description: Too many requests. Wait the number of seconds in `Retry-After`.
      headers:
        Retry-After: { schema: { type: integer } }
      content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }

  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [message]
          properties:
            message: { type: string }

    LoadStatus:
      type: string
      enum: [draft, published, offer_received, carrier_selected, awaiting_carrier_acceptance,
             assigned, pickup_scheduled, picked_up, in_transit, delivered, completed,
             expired, withdrawn, cancelled, reported]

    CreateLoadInput:
      type: object
      required: [title, originCity, originState, destinationCity, destinationState, priceType]
      properties:
        title: { type: string, example: '2 sedans, Miami FL to Atlanta GA' }
        description: { type: [string, 'null'] }
        originCity: { type: string, example: Miami }
        originState: { type: string, example: FL, description: Two-letter state code. }
        originZip: { type: [string, 'null'] }
        destinationCity: { type: string, example: Atlanta }
        destinationState: { type: string, example: GA }
        destinationZip: { type: [string, 'null'] }
        pickupWindowStart: { type: [string, 'null'], format: date, example: '2026-10-02' }
        pickupWindowEnd: { type: [string, 'null'], format: date }
        priceType:
          type: string
          enum: [fixed, offers]
          description: '`fixed` = carriers accept your asking price; `offers` = carriers send their own price.'
        askingPrice: { type: [number, 'null'], example: 1450 }
        declaredValue: { type: [number, 'null'] }
        specialInstructions: { type: [string, 'null'] }
        minAllInPrice: { type: [number, 'null'], description: Optional all-in price floor (USD) for this load. }
        maxAllInPrice: { type: [number, 'null'], description: Optional all-in price ceiling (USD) for this load. }

    UpdateLoadInput:
      type: object
      properties:
        title: { type: string }
        description: { type: [string, 'null'] }
        pickupWindowStart: { type: [string, 'null'], format: date }
        pickupWindowEnd: { type: [string, 'null'], format: date }
        askingPrice: { type: [number, 'null'] }
        declaredValue: { type: [number, 'null'] }
        specialInstructions: { type: [string, 'null'] }
        minAllInPrice: { type: [number, 'null'] }
        maxAllInPrice: { type: [number, 'null'] }

    Load:
      type: object
      description: A load as stored on Carrier-Connect. Field names are snake_case. Additional fields may appear.
      additionalProperties: true
      properties:
        id: { type: string, format: uuid }
        status: { $ref: '#/components/schemas/LoadStatus' }
        title: { type: string }
        description: { type: [string, 'null'] }
        origin_city: { type: string }
        origin_state: { type: string }
        origin_zip: { type: [string, 'null'] }
        destination_city: { type: string }
        destination_state: { type: string }
        destination_zip: { type: [string, 'null'] }
        pickup_window_start: { type: [string, 'null'], format: date }
        pickup_window_end: { type: [string, 'null'], format: date }
        price_type: { type: string, enum: [fixed, offers] }
        asking_price: { type: [number, 'null'] }
        declared_value: { type: [number, 'null'] }
        special_instructions: { type: [string, 'null'] }
        min_all_in_price: { type: [number, 'null'] }
        max_all_in_price: { type: [number, 'null'] }
        published_at: { type: [string, 'null'], format: date-time }
        expires_at: { type: [string, 'null'], format: date-time }
        withdrawn_at: { type: [string, 'null'], format: date-time }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    CarrierSummary:
      type: object
      properties:
        company_name: { type: [string, 'null'] }
        usdot_number: { type: [string, 'null'] }
        mc_number: { type: [string, 'null'] }
        overall_verification_status: { $ref: '#/components/schemas/VerificationStatus' }

    Offer:
      type: object
      additionalProperties: true
      properties:
        id: { type: string, format: uuid }
        load_id: { type: string, format: uuid }
        carrier_id: { type: string, format: uuid, description: 'Use with GET /carriers/{id} and POST /loads/{id}/assign.' }
        offer_price: { type: [number, 'null'] }
        message: { type: [string, 'null'] }
        status: { type: string, example: pending }
        created_at: { type: string, format: date-time }
        responded_at: { type: [string, 'null'], format: date-time }
        carrier:
          oneOf:
            - $ref: '#/components/schemas/CarrierSummary'
            - type: 'null'

    AssignLoadInput:
      type: object
      required: [carrierId, agreedPrice, pickupLocationName, pickupAddress, deliveryLocationName, deliveryAddress]
      properties:
        carrierId: { type: string, format: uuid }
        agreedPrice: { type: number, example: 1400 }
        pickupLocationName: { type: string, example: 'Miami Auto Auction' }
        pickupAddress: { type: string, example: '1234 NW 36th St, Miami, FL 33142' }
        deliveryLocationName: { type: string }
        deliveryAddress: { type: string }

    RateConfirmation:
      type: object
      additionalProperties: true
      properties:
        id: { type: string, format: uuid }
        load_id: { type: string, format: uuid }
        pickup_location_name: { type: string }
        pickup_address: { type: string }
        delivery_location_name: { type: string }
        delivery_address: { type: string }
        poster_signed_at: { type: [string, 'null'], format: date-time }
        carrier_signed_at: { type: [string, 'null'], format: date-time }
        created_at: { type: string, format: date-time }

    RejectReasonCode:
      type: string
      enum: [CARRIER_NOT_FOUND, IDENTITY_MISMATCH, DO_NOT_USE, LANE_RESTRICTION, INSURANCE_FAIL,
             AUTHORITY_FAIL, SAFETY_FAIL, DUPLICATE_AWARD, STALE_POSTING, SYSTEM_ERROR]

    DnuEntryInput:
      type: object
      description: At least one of usdotNumber or mcNumber.
      properties:
        usdotNumber: { type: [string, 'null'] }
        mcNumber: { type: [string, 'null'] }

    DnuEntry:
      type: object
      properties:
        id: { type: string, format: uuid }
        usdot_number: { type: [string, 'null'] }
        mc_number: { type: [string, 'null'] }
        created_at: { type: string, format: date-time }

    VerificationStatus:
      type: string
      enum: [pending, in_review, verified, rejected, expired]

    Carrier:
      type: object
      properties:
        id: { type: string, format: uuid }
        company_name: { type: [string, 'null'] }
        usdot_number: { type: [string, 'null'] }
        mc_number: { type: [string, 'null'] }
        verification:
          type: object
          properties:
            overall:
              $ref: '#/components/schemas/VerificationStatus'
            authority:
              type: object
              description: Operating authority checked against FMCSA.
              properties:
                status: { $ref: '#/components/schemas/VerificationStatus' }
                fmcsa_status: { type: [string, 'null'], example: ACTIVE }
                last_checked_at: { type: [string, 'null'], format: date-time }
            insurance:
              type: object
              properties:
                status: { $ref: '#/components/schemas/VerificationStatus' }
                expires_at: { type: [string, 'null'], format: date }
            motus:
              type: object
              description: Identity check through Motus.
              properties:
                status: { $ref: '#/components/schemas/VerificationStatus' }
                motus_status: { type: [string, 'null'] }
                last_checked_at: { type: [string, 'null'], format: date-time }

    EventType:
      type: string
      enum: [load.created, load.updated, load.covered, load.reopened, load.cancelled, load.award_rejected, load_offer.created]
