> ## Documentation Index
> Fetch the complete documentation index at: https://docs.smartsend.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Print or reprint a document

> Validate a completed document, resolve its target printer and check file/layout compatibility, then create a print attempt. Return 201 as soon as that local job is durably accepted, normally with state pending; external submission and printing continue asynchronously. Read Location with GET or listen for print_job.updated. POST and GET use the same PrintJobResponse schema. A new call without a key creates another attempt; reusing a key never intentionally creates another print. Invalid input, unsupported format or unavailable printer selection returns 422 before creating a job. Failures after an attempt is accepted remain visible on that job.



## OpenAPI

````yaml /openapi-v2beta.json post /teams/{teamUuid}/print-jobs
openapi: 3.1.0
info:
  title: Smart Send API
  description: >
    Shipment-centred API for discovering shipping services, booking immutable
    shipments, tracking parcels, downloading business documents, and printing.


    ## Contract status


    This v2 beta specification describes the target API contract agreed during
    the API design review. It is not a declaration that every operation and
    behaviour has already been deployed. The implementation must be brought into
    conformance before this contract is announced as available.


    ## Shipments and booking


    A shipment represents one logical carrier booking. Integrations own their
    webshop orders and store the returned shipment UUID, tracking numbers,
    documents and drop-off codes on those orders. There is no order
    synchronisation resource and no editing or rebooking of accepted shipment
    input.


    POST /teams/{teamUuid}/shipments validates before persistence and starts
    booking immediately. Upfront validation failure is 422 and creates no
    shipment. A successful booking returns 201. A definitive carrier rejection
    returns 424 with the persisted failed ShipmentResponse. Technical failures
    use appropriate 5xx responses. 424 is the application-specific use of Failed
    Dependency defined by RFC 4918, not a universal carrier convention.


    The server waits up to 10 seconds for the complete booking workflow. If
    processing continues, it returns 202 with ShipmentResponse, Location
    pointing to the ordinary shipment GET, and Retry-After: 5. Follow Location
    with GET. A successful GET always returns 200 with the current
    ShipmentResponse, including while booking is in progress or has failed. The
    response schema is the same; the representation is current, not a frozen
    copy of an earlier HTTP body. The worker's final booking deadline is
    separate from this HTTP wait. A rare late carrier success updates the same
    shipment.


    Booked means the carrier booking has succeeded and all required documents,
    QR codes and drop-off codes are ready. Printing is a separate process and
    does not delay booked. A completed carrier booking must never be repeated to
    recover a document-generation failure. Once the carrier has accepted a
    booking, deletion is forbidden even if document generation subsequently
    fails.


    ## Carrier roles


    Shipment delivery and shipment-specific delivery options expose carrier and
    last_mile_carrier as separate objects, each containing code, name, logo_url
    and icon_url. carrier identifies the provider whose service and account are
    used to book the shipment. service_code continues to identify that carrier's
    service; last_mile_carrier is response data and cannot be selected
    independently in booking input.


    last_mile_carrier is the expected final delivery partner configured for the
    selected shipping route. It is null when unknown. When the booking carrier
    also performs final delivery, return that carrier explicitly in both fields.
    This is planned delivery information, not a confirmation of a physical
    handoff or delivery. Snapshot the expected partner when accepting the
    shipment; later route-configuration changes do not rewrite that shipment's
    attribution. A last-mile partner does not require a separate carrier account
    for the team.


    Last mile follows the physical direction: delivery to the customer for
    outbound shipments, and delivery to the merchant for returns. It does not
    identify the customer's return drop-off network. Carrier branding does not
    change service-point identifiers or tracking numbers and URLs.


    The general GET /teams/{teamUuid}/routes response does not expose
    last_mile_carrier in this version. Its carrier object describes the provider
    whose services the team can book.


    ## Parties, delivery and defaults


    parties.merchant and parties.customer have stable commercial meanings,
    including on returns. When merchant is omitted, use the team's outbound
    sender address or return address as appropriate, falling back to the
    outbound address when no return address is configured. Pickup points,
    selected addons and delivery windows belong to delivery.


    Resolve and snapshot effective booking settings when accepting the shipment,
    including when adding it to an open batch. Later team-setting changes do not
    mutate accepted shipments. This contract has no per-booking overrides of
    team document or automatic-print preferences. Ordered team format
    preferences are negotiated up front; reject an incompatible selection with
    422 rather than silently choosing an unaccepted format. Carrier-specific
    constraints can still cause a subsequent booking failure.


    ## Currency and measurement units


    Each shipment must explicitly supply currency, weight_unit and
    dimension_unit, including each shipment submitted to a batch. The same
    requirement applies to shipment-specific delivery-options input. Missing,
    null or invalid values return 422; these fields never fall back to team
    defaults. Upfront shipment validation creates no shipment, and batch-add
    validation adds none of that request.


    A shipment uses one currency for its monetary amounts, one weight unit for
    all parcel and item weights, and one dimension unit for all parcel and item
    dimensions. Per-value overrides are not supported. Preserve the explicitly
    accepted values in responses. A batch or list may contain shipments with
    different units; each shipment carries its own context. Templates may be
    partial, but the integration must include all three fields in the final
    merged request.


    Service-point proximity results retain a distance object with value and unit
    on each result. Distance is returned only by the closest search, in metres,
    and is not moved into response metadata.


    ## Documents and codes


    Document means a completed business document intended for viewing or
    printing, such as a label, customs declaration, report or end-of-day
    manifest. Documents are team resources and may be associated with shipments,
    reports or manifests. QR images and machine-readable data exports are not
    Document resources.


    Shipment.documents embeds its booking documents, including metadata and
    expiring download URLs, never base64 file data. A document may cover
    multiple parcels or multiple document types. qr_codes and drop_off_codes are
    separate arrays directly on Shipment. A shared QR code is one array entry.
    Where a carrier returns one code per parcel, array index n corresponds to
    parcels[n]; no explicit parcel relation is exposed for these codes. Parcel
    and code order remains stable across responses.


    GET and idempotent replay can refresh file URLs without rebooking,
    generating documents again, or submitting print jobs. Access to an expired
    URL is recovered by retrieving the owning resource again.


    ## Documents, printers and print jobs


    Document represents one completed file. Its metadata is identical when
    embedded in a shipment or returned by document list/detail endpoints. width
    and height are distance objects in millimetres at document level; page_count
    is the number of pages or labels in the file. Every page or label must have
    the same dimensions in its finished orientation. For example, 101.6 × 152.4
    mm labels and an A4 invoice are separate documents with separate files. A
    document can contain multiple pages of the same size. Dimensions and
    page_count are null when unknown or not applicable, such as an XLSX report
    without fixed pagination. Null must not be used to disguise mixed page
    sizes. Store dimensions and page count when producing the file so metadata
    reads do not fetch or parse file contents.


    Document.print_status exposes the progress of the most recently created
    local print attempt directly in document and shipment responses. A null
    print_status means no local print request exists; the field is still
    present. Null does not mean an unknown outcome. pending means the request is
    recorded and Smart Send is preparing or waiting to submit it; queued means
    the print service has accepted it; printing means the provider reports
    processing; printed means the best available successful completion
    acknowledgement; failed means a known failure; unknown means the current
    outcome cannot be established. These attempt states have the same meanings
    as PrintJob.state. Printed currently means delivery to the operating-system
    print queue. Printing a downloaded file outside Smart Send is not observed.


    Select the most recently created print attempt by creation time and a stable
    creation-order tie-breaker, not by updated_at or callback arrival time. A
    new reprint immediately becomes the attempt represented by print_status,
    even after an earlier print succeeded. For example, printed can become
    pending and then failed. Delayed updates to older jobs update their own
    history without overriding the newer attempt's status.


    Retrieve all print attempts separately with GET
    /teams/{teamUuid}/print-jobs?document_uuid=... and follow the cursor until
    the end. Each PrintJob contains its state, fixed printer identity, copy
    count, error and timestamps. Earlier successful attempts remain in the
    history after a later failure. Document.updated_at advances when its
    published print_status or other document metadata changes; each job has its
    own updated_at. Print updates do not advance Shipment.updated_at or change
    its booking or tracking state. On a thin print_job.updated webhook, use
    document_uuid to retrieve the current document; the notification may concern
    an older attempt.


    Printer is current team configuration: identity, display name, availability,
    supported formats and paper_sizes. paper_sizes is null when unknown,
    otherwise an array of driver-advertised named sizes with width and height
    distance objects in millimetres. Individual dimensions are null when only
    the name is known. An empty array means the provider reported no named
    sizes, not that printing or custom sizes are unsupported. Multiple sizes may
    be advertised at once. This list does not map sizes to trays or confirm
    which paper is currently loaded. GET /printers/{printer} returns that
    current resource. Default selection rules are part of team settings and may
    choose different printers for different file formats and page layouts. Print
    jobs embed only the selected printer identity captured for the attempt, so
    later renames, availability changes or removal do not rewrite history. They
    also embed the document identity and display metadata captured when the job
    was created; current document metadata is retrieved through the document
    endpoint.


    One PrintJob represents one logical print attempt for an entire completed
    file and a copy count. All print attempts use the same public
    representation, regardless of how they were initiated. Record accepted print
    intent locally before file preparation or external submission; setup failure
    then produces a failed job rather than disappearing. When booking also
    requests printing, record the corresponding local jobs before returning
    document metadata so the response shows pending, or a later state if already
    reached. Printing itself remains asynchronous. printer may be null if an
    accepted attempt has not selected a target or failed before selection. Once
    selected, the target is fixed for the attempt. POST /print-jobs validates
    its target before accepting a job and returns 422 when invalid. Rejected
    requests create no print attempt and do not change the document's current
    print_status.


    POST /print-jobs returns 201 once the local job is durably accepted,
    normally pending. Printing continues asynchronously; retrieve Location or
    consume print_job.updated notifications. POST and GET return the same
    PrintJobResponse structure. queued means accepted by the print service;
    printed requires its best available completion acknowledgement, currently
    delivery to the operating-system print queue. Printing is independent of
    document readiness and shipment booking state. Duplicate or delayed provider
    updates must not erase a confirmed success; a subsequent successful
    acknowledgement clears a previous failure on the same job. Reprints create
    separate jobs, and unknown outcomes must be checked before requesting
    another print to avoid duplicate output.


    ## Idempotency


    Idempotency-Key is optional on operations that declare it. Without a key,
    each valid create request is independent, even for identical content. No key
    is generated from the payload. Use a fresh client-generated key for each
    intended action and reuse it for recovery after a lost response.


    Keys are scoped to the team, HTTP method and canonical target path. A key
    binds atomically to the accepted action and its resources, with a request
    fingerprint. Object-key ordering is ignored; array order is significant.
    Resolve defaults only once for the accepted action. A changed payload with
    the same key is 422 with a validation error for Idempotency-Key. A
    concurrent duplicate is 409 with Retry-After and, when known, Location. Once
    completed, reconstruct the result from the stored outcome and current
    resource representation and return Idempotency-Replayed: true. URLs and
    subsequent resource state can therefore differ from the first response. For
    shipment creation, the status reflects the booking outcome: 201 for success
    or 424 for carrier rejection, even if a later cancellation has changed the
    current shipment state.


    Retain keys for at least 24 hours from acceptance and never expire an
    actively processing action. After expiry, reusing a key may execute a new
    action. Authentication, authorisation and upfront validation failures do not
    reserve a key. The header does not replace the 202/Location polling flow.
    Idempotency-Replayed is this API's documented extension, not a standard HTTP
    header.


    ## HTTP errors


    Request errors use JSON: message for an ordinary error, with an additional
    errors object for 422 validation failures. There is no custom error code
    field. Use the HTTP status, documented response headers and resource state
    to decide what to do; message text may be localised and can change. Send
    Accept: application/json.


    | Status | Meaning | Client action |

    | --- | --- | --- |

    | 401 | Missing or invalid authentication. | Supply valid credentials. |

    | 403 | Missing permission or scope. | Check team access and permissions. |

    | 404 | Resource unavailable to this team. | Check the resource identifier
    and team. |

    | 409 with Retry-After | An action with this idempotency key is still
    processing. | Wait, then GET Location if supplied; otherwise retry the
    original request with the same key and payload. |

    | 409 without Retry-After | Conflict with resource state or an ambiguous
    tracking lookup. | Retrieve current state or supply the documented tracking
    filters. Do not automatically repeat an unchanged request. |

    | 410 | Tracking cursor expired. | Resynchronise and establish a new feed
    position. |

    | 422 | Invalid input, including reuse of a key with different input. |
    Correct the entries in errors. Upfront booking validation creates no
    shipment; batch-add validation adds none of that request. |

    | 424 | Carrier rejected an accepted booking. | Inspect the persisted failed
    ShipmentResponse. Corrected input requires a new shipment. |

    | 429 | Rate limit reached. | Wait for Retry-After. |

    | 500 | Unexpected server failure. | Recover using the known resource URL or
    original idempotency key; include Request-ID in support reports. |

    | 502 | Accepted booking workflow failed technically. | Inspect the failed
    ShipmentResponse; recover documents without rebooking after carrier
    acceptance. |

    | 503 | Temporary service unavailability. | Wait for Retry-After; preserve
    the original idempotency key when retrying an action. |

    | 504 | Final booking deadline expired. | Inspect the persisted failed
    ShipmentResponse; a rare late carrier success may still update it. |


    The distinction between the two 409 cases using Retry-After is this API's
    convention, not a universal HTTP rule. The usual 10-second booking wait
    returns 202, not 504. A later GET returns 200 when retrieval succeeds, even
    when the shipment's state is failed. Booking-specific 424/502/504 responses
    keep the ShipmentResponse shape rather than switching to a generic
    message-only body.


    ### Invalid shipment input


    ```http

    HTTP/1.1 422 Unprocessable Content

    Content-Type: application/json

    Request-ID: req-example-validation


    {
      "message": "The customer postal code is required.",
      "errors": {
        "parties.customer.address.postal_code": [
          "The customer postal code is required."
        ]
      }
    }

    ```


    ### Batch membership is locked


    ```http

    HTTP/1.1 409 Conflict

    Content-Type: application/json

    Request-ID: req-example-batch


    {
      "message": "Shipments can only be added to an open batch."
    }

    ```


    ### An idempotent request is still processing


    ```http

    HTTP/1.1 409 Conflict

    Content-Type: application/json

    Retry-After: 5

    Request-ID: req-example-processing


    {
      "message": "A request with this Idempotency-Key is still being processed."
    }

    ```


    Location may additionally identify the resource when known. Browser clients
    must be able to read Location, Retry-After, Request-ID and
    Idempotency-Replayed on cross-origin responses; the server exposes these
    through Access-Control-Expose-Headers.


    ## Pagination and synchronisation


    Paginated requests use cursor and per_page for cursor pagination, or page
    and per_page for page pagination. Omit cursor or page for the first page.
    Page numbers start at 1; cursors are opaque. Page-size defaults and limits
    are declared for each endpoint.


    All paginated responses contain data, links and meta. links always contains
    first, last, prev and next. Cursor pagination sets first and last to null;
    its meta contains path, per_page, next_cursor and prev_cursor. Page
    pagination provides first/last URLs and meta.current_page, from, last_page,
    links, path, per_page, to and total. Its meta.links is a navigation window;
    labels are display text, and ellipsis entries omit page. An empty page has
    an empty data array and null from/to. meta.path is the collection URL
    without query parameters.


    Follow the returned navigation URLs with GET. They preserve filters,
    sorting, per_page and the fixed sync boundary where applicable. Changing
    these values starts a new page sequence. Shipments add meta.sync_until and
    tracking adds meta.resume_cursor as endpoint-specific extensions. Cursor
    pagination does not provide a total count or last page number.


    Shipment lists represent current resources, not every intermediate change.
    Shipment.updated_at follows its booking lifecycle, including cancellation;
    tracking changes, print updates and refreshed file URLs do not advance it.
    For incremental sync use inclusive updated_since, sort=updated_at and the
    server-provided sync_until boundary. Follow all next links, then persist
    that boundary. On the next round, subtract an overlap (60 seconds is a
    suggested starting point) and upsert by shipment UUID. Mutable timestamps
    can cause duplicates or omit intermediate states; overlap does not turn this
    into a lossless history. Use include_deleted=true for minimal deletion
    markers and periodic reconciliation where necessary.


    The dedicated tracking/events endpoint is append-only and uses publication
    order, not carrier event time. Its resume_cursor remains usable at the end
    of a page sequence, including empty responses. ETA changes also produce feed
    entries. Tracking and print resources have their own timestamps.


    ## Webhooks and batch callbacks


    Team webhooks are configured through team settings. A batch may additionally
    register its own callback during creation. Both use the same signed thin
    event envelopes: event id, type, occurred_at, team_uuid and affected
    resource identifiers. Retrieve full resource data through the ordinary
    authenticated API when needed. A GET returns current state, which can differ
    from the state when the event occurred. Tracking notifications wake the
    consumer to resume the tracking event feed; deletion notifications identify
    what to remove without requiring a successful GET. Full payloads may be
    added later as an explicit opt-in. Delivery can be duplicated or arrive out
    of order; deduplicate by event id and retrieve the current resource when
    needed. A callback failure never repeats a booking or print action.
    Signatures follow the Standard Webhooks convention. See the callback and
    webhook definitions for delivery and acknowledgement rules.


    ## Localisation and request identification


    Accept-Language takes precedence over the team's default language, with
    English as fallback. Carrier, service and addon codes are stable; names and
    messages may be localised. Error messages are for people, not client control
    flow. A descriptive User-Agent identifies the integration. Request-ID is
    returned on every API response and should be included in support reports.
    Currency amounts use the ISO 4217 currency's minor unit; null means unknown,
    not zero. Dates use RFC 3339 with an explicit UTC offset.
  version: 2.0.0-beta
  contact:
    name: Smart Send
    url: https://www.smartsend.io
  x-contract-status: design
  x-design-defaults:
    http_wait_seconds: 10
    poll_after_seconds: 5
    idempotency_retention_hours: 24
    list_default_per_page: 100
    list_max_per_page: 1000
    batch_add_max_shipments: 100
    webhook_retry_window_hours: 24
    tracking_feed_retention_days: 90
  x-webhook-payload-mode: thin
servers:
  - url: https://api.smartsend.io/v2
    description: Production
security:
  - bearerAuth: []
  - oauth2: []
tags:
  - name: Me
    description: Information about the authenticated user
  - name: Teams
    description: Teams available to the authenticated user
  - name: Service Points
    description: Pickup points and delivery locations
  - name: Carrier Catalog
    description: >-
      Global reference data for carriers, services, and addons. Not
      team-specific.
  - name: Routing
    description: >-
      Team-specific listing of available carriers, services, and addons based on
      configured carrier accounts. Pre-shipment meaning no shipment details
      required.
  - name: Delivery Options
    description: >-
      Shipment-specific delivery options including available services, addons,
      estimated pricing, and delivery windows.
  - name: Tracking
    description: Shipment tracking information and event history
  - name: Shipments
    description: >-
      Immutable booking input, one logical booking per shipment, cancellation
      and synchronisation.
  - name: Booking batches
    description: >-
      Validate shipments before adding them to a batch; after start, book one
      shipment at a time in insertion order.
    externalDocs:
      description: Batch booking documentation
      url: https://docs.smartsend.io/features/booking#batch-booking
  - name: Shipment Templates
    description: >-
      Reusable shipment templates for quickly creating shipments with
      pre-configured delivery, consignment, and parcel settings.
  - name: WMS Experimental
    description: >-
      Warehouse management for a team's configured WMS driver. Covers
      fulfillment requests (listing open requests, inspecting picking lines,
      recording packed lines), inventory levels (listing, moving between bins,
      adjusting counts), and catalog items (SKU lookup).
  - name: Documents
    description: >-
      Completed business documents for viewing or printing, with metadata and
      download links.
  - name: Printing
    description: Discover printers and follow document print jobs.
  - name: Webhooks
    description: Signed resource updates and batch-specific callbacks.
paths:
  /teams/{teamUuid}/print-jobs:
    post:
      tags:
        - Printing
      summary: Print or reprint a document
      description: >-
        Validate a completed document, resolve its target printer and check
        file/layout compatibility, then create a print attempt. Return 201 as
        soon as that local job is durably accepted, normally with state pending;
        external submission and printing continue asynchronously. Read Location
        with GET or listen for print_job.updated. POST and GET use the same
        PrintJobResponse schema. A new call without a key creates another
        attempt; reusing a key never intentionally creates another print.
        Invalid input, unsupported format or unavailable printer selection
        returns 422 before creating a job. Failures after an attempt is accepted
        remain visible on that job.
      operationId: storePrintJob
      parameters:
        - $ref: '#/components/parameters/teamUuid'
        - $ref: '#/components/parameters/Accept-Language'
        - $ref: '#/components/parameters/Request-ID'
        - $ref: '#/components/parameters/Idempotency-Key'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PrintJobInput'
            examples:
              PrintDocumentInput:
                $ref: '#/components/examples/PrintDocumentInput'
              PrintDocumentOnSelectedPrinterInput:
                $ref: '#/components/examples/PrintDocumentOnSelectedPrinterInput'
      responses:
        '201':
          description: Print job created.
          headers:
            Request-ID:
              $ref: '#/components/headers/Request-ID'
            Location:
              $ref: '#/components/headers/Location'
            Idempotency-Replayed:
              $ref: '#/components/headers/Idempotency-Replayed'
            Cache-Control:
              $ref: '#/components/headers/Cache-Control'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PrintJobResponse'
              examples:
                PendingPrintJob:
                  $ref: '#/components/examples/PendingPrintJob'
                QueuedPrintJob:
                  $ref: '#/components/examples/QueuedPrintJob'
        '401':
          $ref: '#/components/responses/ApiUnauthorized'
        '403':
          $ref: '#/components/responses/ApiForbidden'
        '404':
          $ref: '#/components/responses/ApiNotFound'
        '409':
          description: >-
            An action with this Idempotency-Key is still processing. Includes
            Retry-After and, when the resource is known, Location.
          headers:
            Request-ID:
              $ref: '#/components/headers/Request-ID'
            Retry-After:
              $ref: '#/components/headers/Retry-After'
            Location:
              $ref: '#/components/headers/Location'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiErrorResponse'
              examples:
                IdempotencyInProgress:
                  $ref: '#/components/examples/IdempotencyInProgress'
        '422':
          description: >-
            The document, printer or requested print configuration is invalid.
            No print job is created. An idempotency key reused with different
            input also returns 422.
          headers:
            Request-ID:
              $ref: '#/components/headers/Request-ID'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiValidationErrorResponse'
              examples:
                PrintFormatUnsupported:
                  $ref: '#/components/examples/PrintFormatUnsupported'
                PrintTargetUnavailable:
                  $ref: '#/components/examples/PrintTargetUnavailable'
                IdempotencyPayloadMismatch:
                  $ref: '#/components/examples/IdempotencyPayloadMismatch'
        '429':
          $ref: '#/components/responses/ApiTooManyRequests'
        '500':
          $ref: '#/components/responses/ApiInternalServerError'
        '503':
          $ref: '#/components/responses/ApiServiceUnavailable'
      security:
        - bearerAuth: []
        - oauth2:
            - printing:write
components:
  parameters:
    teamUuid:
      name: teamUuid
      in: path
      required: true
      schema:
        type: string
        format: uuid
      description: Team uuid. All resources are scoped to this team.
    Accept-Language:
      name: Accept-Language
      in: header
      required: false
      schema:
        type: string
        default: en
        examples:
          - da-DK, da;q=0.9, en;q=0.8
      description: >-
        Preferred language(s) for translatable content using BCP 47 language
        tags. Supports quality values (q-factors) for priority ordering. The API
        negotiates the best available language based on this header, the team's
        configured language, and available translations. If omitted, the team's
        default language is used.
    Request-ID:
      name: Request-ID
      in: header
      required: false
      schema:
        type: string
        minLength: 1
        maxLength: 64
        pattern: ^[A-Za-z0-9._-]{1,64}$
      description: >-
        Optional client-supplied request identifier. When provided and matching
        the validation pattern, the value is echoed back in the `Request-ID`
        response header. Otherwise the server generates one. Use this to
        correlate requests between client and server when reporting issues.
      examples:
        uuid:
          summary: UUID v7
          value: 019c486f-c237-76f5-85e1-ba7017a77024
        opaque:
          summary: Opaque client-generated key
          value: booking-12345-at-1768609378
    Idempotency-Key:
      name: Idempotency-Key
      in: header
      required: false
      description: >-
        Optional, case-sensitive key for one intended action. Scope: team, HTTP
        method and canonical path. Retained for at least 24 hours and while
        processing. Same key and payload reuses the stored outcome without
        repeating side effects; the resource representation is current. Changed
        payload: 422 with an Idempotency-Key entry in errors. Concurrent
        duplicate: 409 with Retry-After and Location when known. Without a key
        there is no deduplication. Use GET at Location after 202.
      schema:
        type: string
        minLength: 16
        maxLength: 128
        pattern: ^[A-Za-z0-9._-]{16,128}$
      examples:
        uuid:
          summary: UUID v7
          value: 019c4870-44a4-7a63-b9e8-f52cfed69fc2
  schemas:
    PrintJobInput:
      type: object
      properties:
        document_uuid:
          type: string
          description: >-
            Completed Document to print. QR images and data exports are not
            eligible.
          format: uuid
        printer_uuid:
          type: string
          description: >-
            Target printer. Omit to resolve the applicable team print settings
            for the selected file format and page layout. The accepted job
            records the resolved target; later setting changes do not redirect
            it. Missing or incompatible selection returns 422.
          format: uuid
        copies:
          type: integer
          minimum: 1
          maximum: 100
          default: 1
          description: Number of copies of the entire document, including every page.
      required:
        - document_uuid
      description: >-
        Print/reprint request. Validate the document, target printer and
        compatibility before accepting the job. A new POST without an
        idempotency key creates another print job. Copies apply to the complete
        file; per-page selection is not offered by this operation. A rejected
        request creates no job and does not change Document.print_status.
      additionalProperties: false
    PrintJobResponse:
      type: object
      properties:
        data:
          $ref: '#/components/schemas/PrintJob'
      required:
        - data
    ApiErrorResponse:
      type: object
      properties:
        message:
          type: string
          description: >-
            Human-readable, possibly localised explanation. Do not parse this
            text for client control flow.
      required:
        - message
      description: >-
        An unsuccessful API request, using JSON with message and no custom error
        code. Persisted booking outcomes use ShipmentResponse where documented.
    ApiValidationErrorResponse:
      type: object
      properties:
        message:
          type: string
          description: >-
            Human-readable, possibly localised explanation. Do not parse this
            text for client control flow.
        errors:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
          description: >-
            Field or header names mapped to validation messages. Nested input
            uses dot notation, e.g. data.0.parties.customer.address.country.
            Header validation uses the header name, e.g. Idempotency-Key.
      required:
        - message
        - errors
      description: >-
        422 validation response. Field errors use dot notation for nested input;
        Idempotency-Key identifies a header validation error.
    PrintJob:
      type: object
      properties:
        uuid:
          type: string
          format: uuid
          description: Print-job UUID.
        document:
          $ref: '#/components/schemas/DocumentSummary'
        printer:
          anyOf:
            - type: 'null'
            - $ref: '#/components/schemas/PrinterSummary'
          description: >-
            Selected printer identity, fixed for this attempt. Null while the
            attempt has not selected a printer, or when it failed before
            selection. A selected printer remains in this snapshot after
            configuration changes or removal. Jobs in queued, printing or
            printed state always have a selected printer.
        copies:
          type: integer
          minimum: 1
          maximum: 100
          description: Number of copies of the entire document, including every page.
        state:
          $ref: '#/components/schemas/PrintJobState'
        error:
          anyOf:
            - type: 'null'
            - $ref: '#/components/schemas/ApiFailure'
          description: >-
            Actionable failure details when the attempt failed; null when no
            failure is recorded.
        created_at:
          type: string
          format: date-time
          description: When this local print attempt was created.
        updated_at:
          type: string
          format: date-time
          description: Latest meaningful state change of this attempt.
        printed_at:
          type:
            - string
            - 'null'
          format: date-time
          description: >-
            Best available successful print acknowledgement, or null when none
            is known.
      required:
        - uuid
        - document
        - printer
        - copies
        - state
        - error
        - created_at
        - updated_at
        - printed_at
      description: >-
        One logical print attempt for one completed document file and copy
        count. It includes fixed file and printer identity snapshots, plus
        evolving job state. Every attempt uses this representation regardless of
        how it was initiated. Accepted print intent is recorded locally before
        file preparation or external submission, so setup failures are visible
        as jobs, including failures before a printer can be selected. A new
        reprint creates a new job; repeated delivery/status notifications update
        the existing job. Printer configuration changes or removal do not alter
        or delete print history.
    DocumentSummary:
      type: object
      description: >-
        Identity and display metadata of the exact completed file selected for a
        print job. These values are captured when the job is created. Retrieve
        the Document for dimensions and page count, a current download URL and
        the current print_status. Retrieve print-jobs filtered by document_uuid
        for the full attempt history.
      required:
        - uuid
        - name
        - filename
        - format
      properties:
        uuid:
          type: string
          format: uuid
          description: Document UUID.
        name:
          type: string
          description: Document display name captured for this job.
        filename:
          type: string
          description: Download filename captured for this job.
        format:
          $ref: '#/components/schemas/DocumentFormat'
          description: Format of the selected document file.
      additionalProperties: false
    PrinterSummary:
      type: object
      description: >-
        Printer identity captured when the job target is selected. The name
        remains the name used for that job after a printer rename or removal.
        Current availability, capabilities and settings are retrieved through
        the printer endpoints.
      required:
        - uuid
        - name
      properties:
        uuid:
          type: string
          format: uuid
          description: Selected printer UUID.
        name:
          type: string
          description: Printer display name captured for this job.
      additionalProperties: false
    PrintJobState:
      type: string
      enum:
        - pending
        - queued
        - printing
        - printed
        - failed
        - unknown
      description: >-
        pending: Smart Send has recorded a local print attempt and is preparing
        or waiting to submit it to the print service. queued: the print service
        accepted the job but completion is not confirmed. printing: the provider
        reports processing. printed: successful completion according to the best
        available acknowledgement; the current acknowledgement is delivery to
        the operating-system print queue. failed: a known setup, preparation or
        delivery failure. unknown: the outcome cannot currently be established.
        Initial acceptance alone does not mean printed. Future providers may
        supply physical print confirmation.
    ApiFailure:
      type: object
      properties:
        message:
          type: string
          description: >-
            Human-readable, possibly localised explanation. Do not parse this
            text for client control flow.
        source:
          type: string
          enum:
            - carrier
            - system
        fields:
          type: array
          items:
            type: string
          description: Relevant request field paths, if known.
      required:
        - message
        - source
        - fields
      description: >-
        Stored action failure with a human-readable explanation, source and
        relevant input fields. A timeout does not prove that the carrier never
        created a shipment.
    DocumentFormat:
      type: string
      enum:
        - pdf
        - zpl
        - png
        - xlsx
      description: >-
        Business-document file format. XLSX is a report format; direct printing
        is limited by the print service and selected printer.
  examples:
    PrintDocumentInput:
      summary: >-
        Resolve the printer from team settings for this file format and page
        layout.
      value:
        document_uuid: 44444444-4444-4444-8444-444444444444
        copies: 1
    PrintDocumentOnSelectedPrinterInput:
      summary: Print the complete file on a selected printer.
      value:
        document_uuid: 44444444-4444-4444-8444-444444444444
        copies: 1
        printer_uuid: 88888888-8888-4888-8888-888888888888
    PendingPrintJob:
      summary: Accepted local job, awaiting external submission.
      value:
        data:
          uuid: 77777777-7777-4777-8777-777777777777
          document:
            uuid: 44444444-4444-4444-8444-444444444444
            name: Shipping label
            filename: ORDER-1042-label.pdf
            format: pdf
          printer:
            uuid: 88888888-8888-4888-8888-888888888888
            name: Packing station
          copies: 1
          state: pending
          error: null
          created_at: '2026-09-16T10:00:02Z'
          updated_at: '2026-09-16T10:00:02Z'
          printed_at: null
      description: >-
        201 means that the local print request was accepted. It does not confirm
        printer or print-provider acceptance.
    QueuedPrintJob:
      summary: Persisted print request; not yet printed.
      value:
        data:
          uuid: 77777777-7777-4777-8777-777777777777
          document:
            uuid: 44444444-4444-4444-8444-444444444444
            name: Shipping label
            filename: ORDER-1042-label.pdf
            format: pdf
          printer:
            uuid: 88888888-8888-4888-8888-888888888888
            name: Packing station
          copies: 1
          state: queued
          error: null
          created_at: '2026-09-16T10:00:02Z'
          updated_at: '2026-09-16T10:00:02Z'
          printed_at: null
    IdempotencyInProgress:
      summary: 409 — Original request still processing
      value:
        message: A request with this Idempotency-Key is still being processed.
      description: >-
        Includes Retry-After: 5 and may include Location when the resource is
        known. Wait, then GET Location if provided; otherwise retry the original
        request with the same key and payload.
    PrintFormatUnsupported:
      summary: 422 — The selected printer does not accept this document format.
      value:
        message: The selected printer does not accept this document format.
        errors:
          document_uuid:
            - The selected printer does not accept this document format.
      description: The explicit print request failed validation before a job was created.
    PrintTargetUnavailable:
      summary: 422 — No compatible printer is configured for this document layout.
      value:
        message: No compatible printer is configured for this document layout.
        errors:
          printer_uuid:
            - No compatible printer is configured for this document layout.
      description: The explicit print request failed validation before a job was created.
    IdempotencyPayloadMismatch:
      summary: 422 — Same key used with different input
      value:
        message: The Idempotency-Key has already been used with different request data.
        errors:
          Idempotency-Key:
            - >-
              Use the original request data, or a new key for a separate
              intended action.
      description: >-
        The original action is unaffected. Do not generate a new key merely to
        retry an action whose result is still unknown.
    Unauthenticated:
      summary: 401 — Missing or invalid credentials
      value:
        message: Unauthenticated.
      description: >-
        Supply a valid API token. Repeating the same credentials does not
        resolve this response.
    PermissionDenied:
      summary: 403 — Insufficient team permission
      value:
        message: This action is unauthorized.
      description: Use credentials with the required team permission or scope.
    ResourceNotFound:
      summary: 404 — Resource unavailable to this team
      value:
        message: The requested resource was not found.
      description: >-
        Check the resource UUID and team. A missing, deleted or other-team
        resource uses the same response.
    TooManyRequests:
      summary: 429 — Rate limit reached
      value:
        message: Too Many Attempts.
      description: >-
        Wait for the number of seconds in Retry-After before sending another
        request.
    ServerError:
      summary: 500 — Unexpected server failure
      value:
        message: Server Error
      description: >-
        An action may already have been accepted. Recover through its known
        resource URL or retry with the original Idempotency-Key. Include
        Request-ID when contacting support.
    ServiceUnavailable:
      summary: 503 — Temporary unavailability
      value:
        message: Service Unavailable
      description: >-
        Wait for Retry-After before retrying. Preserve the original
        Idempotency-Key for an action that may already have been accepted.
  headers:
    Request-ID:
      description: >-
        Unique identifier for this HTTP request. Always present in responses.
        Echoes the client-supplied `Request-ID` request header when it matches
        the validation pattern; otherwise the server generates a UUIDv7. Include
        this value when reporting issues so support can locate the request in
        logs.
      schema:
        type: string
        minLength: 1
        maxLength: 64
        pattern: ^[A-Za-z0-9._-]{1,64}$
    Location:
      description: >-
        Absolute API URL of the accepted resource. For booking or cancellation
        polling, this is the ordinary shipment GET URL.
      schema:
        type: string
        format: uri
    Idempotency-Replayed:
      description: >-
        True when this response reuses a previously accepted action. Its body is
        reconstructed from the current resource and stored outcome;
        byte-identical response caching is not promised. API-specific header.
      schema:
        type: boolean
        examples:
          - true
    Cache-Control:
      description: >-
        Shipment, document, tracking and print responses contain private data
        and expiring URLs.
      schema:
        type: string
        const: private, no-store
    Retry-After:
      description: >-
        Delay in seconds before the next polling or retry request. Booking
        polling normally uses 5.
      schema:
        type: integer
        minimum: 1
        examples:
          - 5
  responses:
    ApiUnauthorized:
      description: Authentication is missing or invalid.
      headers:
        Request-ID:
          $ref: '#/components/headers/Request-ID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
          examples:
            Unauthenticated:
              $ref: '#/components/examples/Unauthenticated'
    ApiForbidden:
      description: The authenticated principal lacks the required team permission or scope.
      headers:
        Request-ID:
          $ref: '#/components/headers/Request-ID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
          examples:
            PermissionDenied:
              $ref: '#/components/examples/PermissionDenied'
    ApiNotFound:
      description: The resource is absent, belongs to another team, or has been deleted.
      headers:
        Request-ID:
          $ref: '#/components/headers/Request-ID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
          examples:
            ResourceNotFound:
              $ref: '#/components/examples/ResourceNotFound'
    ApiTooManyRequests:
      description: Rate limit exceeded. Wait for Retry-After before retrying.
      headers:
        Request-ID:
          $ref: '#/components/headers/Request-ID'
        Retry-After:
          $ref: '#/components/headers/Retry-After'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
          examples:
            TooManyRequests:
              $ref: '#/components/examples/TooManyRequests'
    ApiInternalServerError:
      description: >-
        Unexpected server failure. An accepted action may already exist; recover
        using the same idempotency key or known resource URL.
      headers:
        Request-ID:
          $ref: '#/components/headers/Request-ID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
          examples:
            ServerError:
              $ref: '#/components/examples/ServerError'
    ApiServiceUnavailable:
      description: The service is temporarily unavailable.
      headers:
        Request-ID:
          $ref: '#/components/headers/Request-ID'
        Retry-After:
          $ref: '#/components/headers/Retry-After'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiErrorResponse'
          examples:
            ServiceUnavailable:
              $ref: '#/components/examples/ServiceUnavailable'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
    oauth2:
      type: oauth2
      description: OAuth 2.0 Authorization Code flow for API access
      flows:
        authorizationCode:
          authorizationUrl: https://app.smartsend.io/oauth/authorize
          tokenUrl: https://app.smartsend.io/oauth/token
          scopes:
            teams:read: Access team information
            members:read: Read team member information
            members:write: Manage team members
            service_points: Access service points and pickup locations
            routing: Access team-specific routing (pre-shipment discovery)
            delivery-options:read: Get shipping rates and pricing
            tracking:read: Access shipment tracking information
            shipment:read: Read shipment information
            shipment:write: Create and manage shipments
            inventory:read: Read team WMS inventory levels and items
            inventory:write: Mutate team WMS inventory (move stock, update counts)
            fulfillment:read: Read team WMS fulfillment requests
            fulfillment:write: Mutate team WMS fulfillment requests (pack)
            documents:read: Read team business documents, including reports and manifests
            printing:read: Read printers and document print jobs
            printing:write: Create document print and reprint jobs

````