> ## Documentation Index
> Fetch the complete documentation index at: https://partner-help.letsdothis.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Ingest timing data from a timing provider

> Available to select timing partners on a per-account basis — contact your account manager to enable it. Authenticate with the timing-provider token issued during onboarding as a bearer token; other callers receive `401`.

Push data — bib numbers and bib-pack shipments — back to Let's Do This for startlist entries you received from us. Send one message or a batch of up to 500, each carrying at least one recognized field in `data`.

A `202` means the batch was accepted for processing, not that values were applied. Messages are applied asynchronously, and an update whose `changedAt` is older than the last applied one for the same field is skipped — bib and shipping updates are ordered independently. `messageId` de-duplicates retries, so resubmitting an entire request after a timeout or `5xx` response is safe.

Rate limit as of today: 20 requests per second.



## OpenAPI

````yaml /openapi.json post /timing-inbound/v1
openapi: 3.1.0
info:
  title: Let's Do This API
  version: 0.1.0
  description: >
    The full reference — getting started, pagination, error responses, rate
    limits and the changelog — lives at

    [partner-help.letsdothis.com/api-reference](https://partner-help.letsdothis.com/api-reference/overview).

    This page is the interactive OpenAPI view of the same specification.


    # Authentication


    Create an API key from **Settings** → **Credentials** in your Let's Do This
    account and send it as a bearer token:


    ```bash

    curl --request GET \
     --url "https://api.letsdothis.com/v0/participants" \
     --header "Authorization: Bearer YOUR-API-KEY"
    ```


    Keep the key secret: it identifies your organization, so do not ship it in a
    website or native app. If a key is exposed,

    revoke it from the same section and create a replacement.


    # OpenAPI schema


    Download the [OpenAPI schema](https://api.letsdothis.com/documentation/json)
    to use with Postman or an OpenAPI client

    generator.
servers:
  - url: https://api.letsdothis.com
    description: Production
  - url: https://api.staging.letsdothis.com
    description: Staging
security:
  - bearerAuth: []
tags:
  - name: Events
    x-group: Events
    description: Events that participants can register for.
  - name: EventOccurrences
    x-group: Event occurrences
    description: >-
      Event occurrences scoped to the organizer associated with your Public API
      credentials. Unlike the public Events catalog, results are limited to your
      own events.
  - name: Races
    x-group: Races
    description: >-
      Races within an event occurrence. An event occurrence may have one or more
      races, for example a 5K and a 10K within the same race day.
  - name: Tickets
    x-group: Tickets
    description: Tickets that are available for events.
  - name: BookingForms
    x-group: Booking forms
    description: >-
      Booking forms define the questions a booker answers when registering for a
      ticket. A ticket usually references a single booking form, so expect zero
      or one, while a booking form may be shared across multiple tickets.
  - name: BookingFormFields
    x-group: Booking form fields
    description: >-
      Booking form fields are the individual questions on a booking form — their
      type, label, options, and whether an answer is required. A field can be
      reused across multiple forms.
  - name: Bookings
    x-group: Bookings
    description: >-
      Bookings are the transaction record for a registration. A booking may
      contain one or more participants (entries).
  - name: Participant
    x-group: Participants
    description: Participants are users who successfully booked an event.
  - name: LineItem
    x-group: Line items
    description: Financial details related to individual items within transactions.
  - name: AddOns
    x-group: Add-ons
    description: >-
      Organizer-owned products that can be purchased and allocated to
      participants. Imported products retain stable external identities.
  - name: DiscountCodes
    x-group: Discount codes
    description: >-
      Discount codes participants enter at checkout to reduce the price of a
      booking.
  - name: Credits
    x-group: Credits
    description: >-
      Credits are balances participants can spend at checkout, identified by
      their email address.
  - name: Referrals
    x-group: Referrals
    description: >-
      Referral programs, the referrers registered on them, and asynchronous
      imports of historical referrals.
  - name: Application
    x-group: Applications
    description: >-
      Applications are created when a user applies for tickets that are set up
      to require an application process, such as balloted or "Good For Age"
      entries.
  - name: Partner
    x-group: Partners
    description: >-
      Partners that were created by the organization who can have Reserved
      Entries assigned to them.
  - name: ReservedEntries
    x-group: Reserved entries
    description: >-
      Reserved Entries are entries that are reserved for a partner. After being
      assigned, the partner can then allocate these entries to participants.
  - name: Timing
    x-group: Timing
    description: >-
      Timing providers push bib numbers and bib-pack shipments back to Let's Do
      This for the startlist entries they received. Available to select timing
      partners only.
paths:
  /timing-inbound/v1:
    post:
      tags:
        - Timing
      summary: Ingest timing data from a timing provider
      description: >-
        Available to select timing partners on a per-account basis — contact
        your account manager to enable it. Authenticate with the timing-provider
        token issued during onboarding as a bearer token; other callers receive
        `401`.


        Push data — bib numbers and bib-pack shipments — back to Let's Do This
        for startlist entries you received from us. Send one message or a batch
        of up to 500, each carrying at least one recognized field in `data`.


        A `202` means the batch was accepted for processing, not that values
        were applied. Messages are applied asynchronously, and an update whose
        `changedAt` is older than the last applied one for the same field is
        skipped — bib and shipping updates are ordered independently.
        `messageId` de-duplicates retries, so resubmitting an entire request
        after a timeout or `5xx` response is safe.


        Rate limit as of today: 20 requests per second.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TimingInboundRequestBody'
      responses:
        '202':
          description: Default Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TimingInboundAcceptedResponse'
        '400':
          description: Malformed message or unknown schemaVersion
          content:
            application/json:
              schema:
                description: Malformed message or unknown schemaVersion
                type: object
                properties:
                  statusCode:
                    type: number
                    enum:
                      - 400
                  error:
                    type: string
                  message:
                    type: string
        '403':
          description: Message provider does not match the credential
          content:
            application/json:
              schema:
                description: Message provider does not match the credential
                type: object
                properties:
                  statusCode:
                    type: number
                    enum:
                      - 403
                  error:
                    type: string
                  message:
                    type: string
        '429':
          description: Rate limit exceeded
          headers:
            retry-after:
              schema:
                type: integer
              description: Seconds to wait before retrying the request.
            x-ratelimit-limit:
              schema:
                type: integer
              description: Requests permitted in the current window.
            x-ratelimit-remaining:
              schema:
                type: integer
              description: Requests still permitted in the current window.
            x-ratelimit-reset:
              schema:
                type: integer
              description: Seconds until the current window resets.
          content:
            application/json:
              schema:
                description: Rate limit exceeded
                type: object
                properties:
                  statusCode:
                    type: number
                    enum:
                      - 429
                  error:
                    type: string
                  message:
                    type: string
components:
  schemas:
    TimingInboundRequestBody:
      title: TimingInboundRequestBody
      anyOf:
        - $ref: '#/components/schemas/TimingInboundMessage'
        - $ref: '#/components/schemas/TimingInboundBatch'
      description: Body of POST /timing-inbound/v1 — a single message or a batch.
    TimingInboundAcceptedResponse:
      title: TimingInboundAcceptedResponse
      type: object
      properties:
        received:
          type: number
        messageIds:
          type: array
          items:
            type: string
      required:
        - received
        - messageIds
      additionalProperties: false
      description: >-
        Acknowledgement that messages were received and queued — NOT yet
        applied; messages are applied asynchronously, and any per-record failure
        is reported separately rather than in this response.
    TimingInboundMessage:
      title: TimingInboundMessage
      type: object
      properties:
        schemaVersion:
          type: string
          description: 'Contract schema version. Currently supported: "1.0".'
        messageId:
          type: string
          description: >-
            Provider-generated unique id for this message — the idempotency and
            dead-letter correlation key.
          minLength: 1
        provider:
          type: string
          description: >-
            The timing provider code issued to you by Let's Do This. Must match
            the provider the bearer token was issued for.
          minLength: 1
        ldtStartlistEntryId:
          type: string
          description: >-
            The LDT startlist entry id the data applies to — the join key LDT
            sent outbound.
          minLength: 1
        data:
          $ref: '#/components/schemas/TimingInboundData'
        changedAt:
          type: string
          description: When the value changed at the provider (last-writer-wins ordering).
          format: date-time
        providerRef:
          $ref: '#/components/schemas/TimingInboundProviderRef'
      required:
        - schemaVersion
        - messageId
        - provider
        - ldtStartlistEntryId
        - data
        - changedAt
      additionalProperties: {}
      description: >-
        One timing-inbound contract-v1 message: a timing provider reporting data
        (a bib number, a shipment, or both) for one LDT startlist entry.
    TimingInboundBatch:
      title: TimingInboundBatch
      type: array
      items:
        $ref: '#/components/schemas/TimingInboundMessage'
      description: >-
        A batch of timing-inbound messages. maxItems documents the contract cap;
        enforcement is the handler's MAX_MESSAGES_PER_REQUEST check (route-level
        schema validation is disabled in this service).
      maxItems: 500
    TimingInboundData:
      title: TimingInboundData
      type: object
      properties:
        bibNumber:
          type:
            - string
            - 'null'
          description: >-
            The bib number to apply, or null to remove the participant's bib
            (e.g. a bib reported in error). Omission means "no change". The cap
            is generous against real bib formats (numeric or short alphanumeric)
            while keeping an oversized value out of the check-in sync path,
            exports and comms it would otherwise reach.
          minLength: 1
          maxLength: 32
        shipping:
          $ref: '#/components/schemas/TimingInboundShipping'
        checkedInAt:
          type: string
          description: >-
            When the participant checked in at the event. Applied like a
            check-in made with the Let's Do This check-in app. Omission means
            "no change" — there is no way to clear a recorded check-in over this
            contract.
          format: date-time
      additionalProperties: {}
      description: >-
        Timing data for one participant. Every field is optional here because a
        provider may report them in separate messages, but a message must carry
        at least one field this integration recognises — enforced in the
        handler, not in this type, because the index signature below leaves the
        generated JSON schema unable to express it. Unknown fields are still
        accepted and passed through to the timing pipeline untouched.
    TimingInboundProviderRef:
      title: TimingInboundProviderRef
      type: object
      properties:
        account:
          type: string
          description: The provider's per-client account id.
        event:
          type: string
          description: The provider's event id.
        race:
          type: string
          description: The provider's race id.
        participant:
          type: string
          description: >-
            The provider's participation id. Carried through untouched so a
            message can be traced back to a specific record on the provider's
            side when debugging what was exchanged.
      additionalProperties: false
      description: >-
        Provider-native identifiers on a timing-inbound message — optional
        context that is not processed by Let's Do This today; safe to omit.
    TimingInboundShipping:
      title: TimingInboundShipping
      type: object
      properties:
        shippedAt:
          type: string
          description: When the pack was dispatched.
          format: date-time
        status:
          $ref: '#/components/schemas/TimingInboundShippingStatus'
        carrier:
          type: string
          description: >-
            Carrier as the provider names it, e.g. "Royal Mail". Free text —
            stored and displayed, never parsed.
          minLength: 1
          maxLength: 128
        trackingNumber:
          type: string
          minLength: 1
          maxLength: 128
        trackingUrl:
          type: string
          description: >-
            Deliberately not `@format uri`: the value is opaque to us and never
            dereferenced, so a malformed one must not 400 away the whole
            shipment.
          minLength: 1
          maxLength: 2048
        shippingId:
          type: string
          description: >-
            The carrier's internal shipment id (e.g. a DHL shipment id) —
            distinct from the customer-facing trackingNumber. Opaque to us:
            stored and displayed for support, never parsed.
          minLength: 1
          maxLength: 128
      required:
        - shippedAt
      additionalProperties: false
      description: >-
        Dispatch of the participant's bib pack. `shippedAt` is required on every
        shipping message, later status updates included — relaxing that to
        optional is additive for the provider later, tightening it would not be.


        A field the message omits leaves the stored value alone, so an update
        need only carry what changed; it cannot clear a carrier or tracking
        number.
    TimingInboundShippingStatus:
      title: TimingInboundShippingStatus
      type: string
      enum:
        - SHIPPED
        - DELIVERED
        - RETURNED
      description: >-
        Status of a participant's shipment. An unlisted value is rejected with a
        400 rather than stored, so the provider gets an error they can act on
        instead of a silent drop; widening the vocabulary is a one-line change
        here and in the timing-inbound zod mirror.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

````