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

# Append photo notes to a work order in bulk

> Appends up to 25 photo-only notes to the work order's buyer-supplier note thread in one call and sends a single grouped notification instead of one notification per photo. Each note must carry a photo URL and no other content (empty text, no video, audio, otherFile, or elements), and the same photo URL cannot appear twice in one request. Returns the updated work order.



## OpenAPI

````yaml /openapi/supplier.json patch /v1/supplier/work_order/work_orders/append_notes/bulk
openapi: 3.1.0
info:
  title: OpenWrench Supplier API
  version: 1.0.0
  description: >-
    External REST API for OpenWrench suppliers (service providers).


    ## Authentication

    Every request must carry a supplier API key in the `X-API-KEY` header. The
    key resolves to a supplier contact; all reads and writes are tenant-scoped
    to that contact's supplier facility/company. Missing or invalid keys are
    answered with 401.


    ## Rate limiting

    10 requests per 20-second window per API key. Requests beyond that are
    answered with 429 and the standard error envelope. (A few endpoints, noted
    in their descriptions, are not rate-limited.)


    ## Response envelope

    Single entity: `{"type": "<EntityName>", "data": {...}, "status": "ok"}`.
    Lists: `{"type": "<EntityName>", "data": [...], "count": <total>, "status":
    "ok"}`. count_by endpoints return the integer count in `data`. The `type`
    label is hard-coded on some endpoints (e.g. "WorkOrderNotes", "pingpong")
    and derived via Scala reflection on generic CRUD endpoints, where it may
    appear as the fully qualified server class name - treat it as informational.
    Errors: `{"message": "...", "type": "<ExceptionType>", "status": "error",
    "traceId": "..."}` with HTTP 400 (bad input, and also permission-denied
    reads/writes), 401 (missing/invalid key), 404 (not found), 429 (rate limit).


    ## Pagination

    List endpoints accept `offset`, `limit` (default 10, max 25 - the supplier
    locations list caps at 10), `sort_by` and `order` (`asc`|`desc`), plus
    entity-specific query-string filters on indexed columns.


    ## Data masking

    For third-party suppliers (keys whose supplier company is not a buyer's
    internal service team), work order, location and invoice responses are
    masked: buyer-private fields are blanked before the response is returned.


    ## Date formats

    Date-time fields serialize as strings in one of two shapes depending on the
    underlying type: ISO 8601 with offset (e.g. `2026-08-14T13:05:22.000-07:00`)
    for most timestamps, or `yyyy-MM-dd HH:mm:ss.S` (space-separated, no offset)
    for database timestamp fields. Plain dates are `yyyy-MM-dd`. When sending
    date-times, ISO 8601 is accepted.


    ## Required headers

    Every request must carry BOTH `X-API-KEY` (your API key) and `OW-KEY` (the
    OpenWrench shared secret issued with it). Requests missing either return
    401.
servers:
  - url: https://api.useopenwrench.com/api/external
security:
  - ApiKeyAuth: []
    OwKeyAuth: []
tags:
  - name: Ping
  - name: Buyer Companies
  - name: Locations
  - name: Regions
  - name: Asset Types
  - name: Assets
  - name: Asset Labels
    description: Read the asset label catalog and replace the labels applied to an asset.
  - name: Work Orders
  - name: Work Order Labels
    description: >-
      Read the work order label catalog visible to your key and replace the
      labels applied to a work order.
  - name: Service Calls
  - name: WrenchMode
    description: Read-only WrenchMode work logs and technician time analytics
  - name: Proposals
  - name: Invoices
  - name: Files
  - name: Purchase Requests
  - name: Purchase Orders
  - name: Purchase Order Receipts
  - name: Parts
  - name: Equipment Types
  - name: Stock Location Inventory
  - name: Vendors
  - name: Users
paths:
  /v1/supplier/work_order/work_orders/append_notes/bulk:
    patch:
      tags:
        - Work Orders
      summary: Append photo notes to a work order in bulk
      description: >-
        Appends up to 25 photo-only notes to the work order's buyer-supplier
        note thread in one call and sends a single grouped notification instead
        of one notification per photo. Each note must carry a photo URL and no
        other content (empty text, no video, audio, otherFile, or elements), and
        the same photo URL cannot appear twice in one request. Returns the
        updated work order.
      operationId: bulkAppendWorkOrderNotes
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                id:
                  type: integer
                  format: int64
                  description: Work order id (required; must exist)
                notes:
                  type: array
                  items:
                    $ref: '#/components/schemas/Note'
                  minItems: 1
                  maxItems: 25
                  description: >-
                    Photo-only notes to append, in the order they should appear
                    on the thread. Each entry must set photo and leave every
                    other content field empty; photo URLs must be unique within
                    the request.
              required:
                - id
                - notes
              description: >-
                The work order id plus 1-25 photo-only notes. Every note's
                noteAddedBy is set to the calling contact and noteAddedAt is
                overwritten with the server's current time; the author's email
                is added to the work order's supplier subscriber list.
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WorkOrderResponse'
        '400':
          description: >-
            Validation failure: notes is empty, has more than 25 entries,
            contains a note that is not photo-only, repeats a photo URL, or the
            payload could not be parsed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '401':
          description: Missing or invalid X-API-KEY
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Rate limit exceeded (10 requests per 20-second window)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    Note:
      type: object
      properties:
        id:
          anyOf:
            - type: integer
              format: int64
              description: >-
                Per-note row id, populated on read where the note store is
                normalized
            - type: 'null'
        text:
          type: string
        photo:
          anyOf:
            - type: string
            - type: 'null'
        video:
          anyOf:
            - type: string
            - type: 'null'
        audio:
          anyOf:
            - type: string
            - type: 'null'
        otherFile:
          anyOf:
            - type: string
            - type: 'null'
        noteAddedBy:
          type: string
          description: Email of the author
        noteAddedAt:
          type: string
          format: date-time
          description: ISO 8601 date-time
        elements:
          anyOf:
            - type: array
              items:
                type: object
                properties:
                  type:
                    type: string
                  value:
                    type: string
                  userObject:
                    anyOf:
                      - $ref: '#/components/schemas/Contact'
                      - type: 'null'
            - type: 'null'
        noteReadDetails:
          anyOf:
            - type: array
              items:
                type: object
                properties:
                  readAt:
                    type: string
                    format: date-time
                    description: ISO 8601 date-time
                  readBy:
                    type: string
                  readByContact:
                    anyOf:
                      - $ref: '#/components/schemas/Contact'
                      - type: 'null'
            - type: 'null'
        noteAddedByContact:
          anyOf:
            - $ref: '#/components/schemas/Contact'
            - type: 'null'
        noteType:
          anyOf:
            - type: string
            - type: 'null'
        noteAddedByType:
          anyOf:
            - type: string
            - type: 'null'
        caption:
          anyOf:
            - type: string
            - type: 'null'
        isNoteAwaitingResponse:
          anyOf:
            - type: boolean
            - type: 'null'
      required:
        - text
        - noteAddedBy
        - noteAddedAt
      description: >-
        Work-order note (abridged - additional media/caption/transcription
        fields exist).
      additionalProperties: true
    WorkOrderResponse:
      type: object
      properties:
        type:
          type: string
          description: >-
            Entity type label, e.g. "WorkOrder". Endpoints that derive it via
            Scala reflection (most generic list/get endpoints) may return the
            fully qualified server class name instead (e.g.
            "models.workorder.WorkOrder"); treat this field as informational.
        data:
          $ref: '#/components/schemas/WorkOrder'
        status:
          type: string
          description: '"ok" on success'
          enum:
            - ok
      required:
        - type
        - data
        - status
    Error:
      type: object
      properties:
        message:
          type: string
          description: Human-readable error message
        type:
          type: string
          description: >-
            Exception type identifier. Known values (exact casing):
            "unauthorizedException", "authorizationException", "ParseException",
            "UnexpectedException", "NotFoundException", "BadRequestException",
            "PaymentRequiredException", "InvalidInputDataException",
            "ConflictException", "ForbiddenException", "InvalidInputException"
        status:
          type: string
          enum:
            - error
        traceId:
          type: string
          description: >-
            Server-generated trace identifier for support (11 alphanumeric
            characters)
      required:
        - message
        - type
        - status
        - traceId
      description: Standard error envelope returned for 4xx/5xx responses.
    Contact:
      type: object
      properties:
        email:
          type: string
        nameGiven:
          type: string
        nameFamily:
          type: string
        title:
          anyOf:
            - type: string
            - type: 'null'
        department:
          anyOf:
            - type: string
            - type: 'null'
        facilityId:
          anyOf:
            - type: integer
            - type: 'null'
        contactType:
          type: string
          description: '"buyer" or "supplier"'
        isSharedContact:
          type: boolean
        readOnlyAccess:
          anyOf:
            - type: boolean
            - type: 'null'
        invitedByEmail:
          anyOf:
            - type: string
            - type: 'null'
        invitedAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        epaCertificationType:
          anyOf:
            - type: string
            - type: 'null'
        epaCertificationNumber:
          anyOf:
            - type: string
            - type: 'null'
        createdAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        updatedAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
      required:
        - email
        - nameGiven
        - nameFamily
        - contactType
      description: >-
        Contact record (abridged - the full Contact model carries additional
        fields).
      additionalProperties: true
    WorkOrder:
      type: object
      properties:
        id:
          anyOf:
            - type: integer
              format: int64
            - type: 'null'
        buyerSpecificId:
          anyOf:
            - type: string
            - type: 'null'
        woNumber:
          anyOf:
            - type: string
            - type: 'null'
        poNumber:
          anyOf:
            - type: string
            - type: 'null'
        isPM:
          type: boolean
          description: Planned-maintenance work order
        isCapex:
          type: boolean
        isStale:
          type: boolean
        isSupplierInitiated:
          type: boolean
        isCustomerInitiated:
          type: boolean
        isWarranty:
          type: boolean
        title:
          type: string
        displayStatus:
          type: string
        status:
          type: string
        statusChangedAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        description:
          anyOf:
            - type: string
            - type: 'null'
        assetId:
          anyOf:
            - type: integer
              format: int64
            - type: 'null'
        subAssetIds:
          anyOf:
            - type: array
              items:
                type: integer
                format: int64
            - type: 'null'
        isAssetDown:
          type: boolean
        locationId:
          type: integer
          format: int64
        areaId:
          anyOf:
            - type: integer
              format: int64
            - type: 'null'
        workCategoryId:
          anyOf:
            - type: integer
            - type: 'null'
        spendCategoryId:
          anyOf:
            - type: integer
            - type: 'null'
        problemTypeId:
          type: integer
        problemType:
          anyOf:
            - $ref: '#/components/schemas/ProblemType'
            - type: 'null'
        woPriorityId:
          anyOf:
            - type: integer
            - type: 'null'
        needsApproval:
          type: boolean
        images:
          type: array
          items:
            type: string
        dueDate:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        scheduledAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        estimatedCompletionDate:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        completedAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        assignedAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        supplierRespondedAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        supplierFacilityId:
          anyOf:
            - type: integer
            - type: 'null'
        supplierPrimaryContactEmail:
          anyOf:
            - type: string
            - type: 'null'
        price:
          anyOf:
            - type: number
            - type: 'null'
        currencyId:
          type: string
        nte:
          anyOf:
            - type: number
              description: Not-to-exceed amount
            - type: 'null'
        notes:
          type: array
          items:
            $ref: '#/components/schemas/Note'
        lastNote:
          anyOf:
            - type: string
            - type: 'null'
        lastNoteAddedBy:
          anyOf:
            - type: string
            - type: 'null'
        lastNoteAddedAt:
          anyOf:
            - type: integer
              format: int64
              description: Epoch millis
            - type: 'null'
        buyerFacilityId:
          type: integer
        buyerCompanyId:
          type: integer
        createdBy:
          type: string
        buyerSubscriberEmails:
          type: array
          items:
            type: string
        supplierSubscriberEmails:
          type: array
          items:
            type: string
        associatedServiceCalls:
          type: array
          items:
            $ref: '#/components/schemas/ServiceCall'
        lastServiceCall:
          anyOf:
            - $ref: '#/components/schemas/ServiceCall'
            - type: 'null'
        buyerAttachments:
          type: array
          items:
            $ref: '#/components/schemas/FileDetails'
        supplierAttachments:
          type: array
          items:
            $ref: '#/components/schemas/FileDetails'
        resolutionTypeId:
          anyOf:
            - type: integer
            - type: 'null'
        plannedMaintenanceScheduleId:
          anyOf:
            - type: integer
              format: int64
            - type: 'null'
        cancelledAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        cancelledBy:
          anyOf:
            - type: string
            - type: 'null'
        glCode:
          anyOf:
            - type: string
            - type: 'null'
        createdAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        updatedAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        isDeleted:
          type: boolean
        deletedAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
      description: >-
        Work order (abridged - the full model has 150+ fields including hydrated
        location/asset/facility/approval-hierarchy objects). For third-party
        suppliers (keys not belonging to an internal service team) responses are
        masked: buyer-private fields are removed/blanked by the API masking
        layer.
      additionalProperties: true
    ProblemType:
      type: object
      properties:
        id:
          anyOf:
            - type: integer
            - type: 'null'
        name:
          type: string
        buyerCompanyId:
          type: integer
        hasChildren:
          type: boolean
      description: >-
        Problem type (abridged - model source not available; fields inferred
        from controller usage).
      additionalProperties: true
    ServiceCall:
      type: object
      properties:
        id:
          anyOf:
            - type: integer
              format: int64
            - type: 'null'
        leadTechnicianEmail:
          anyOf:
            - type: string
            - type: 'null'
        leadTechnicianEmailContact:
          anyOf:
            - $ref: '#/components/schemas/Contact'
            - type: 'null'
        additionalTechnicianEmails:
          type: array
          items:
            type: string
        additionalTechnicians:
          type: array
          items:
            $ref: '#/components/schemas/Contact'
        stockLocationIds:
          type: array
          items:
            type: integer
            format: int64
        stockLocations:
          type: array
          items:
            $ref: '#/components/schemas/StockLocation'
        supplierFacilityId:
          type: integer
        serviceScheduledAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        checkInByEmail:
          anyOf:
            - type: string
            - type: 'null'
        checkInByContact:
          anyOf:
            - $ref: '#/components/schemas/Contact'
            - type: 'null'
        checkInStatus:
          anyOf:
            - type: string
            - type: 'null'
        checkInTime:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        checkInNotes:
          anyOf:
            - type: string
            - type: 'null'
        checkInImages:
          type: array
          items:
            type: string
        checkInGeoLocation:
          anyOf:
            - $ref: '#/components/schemas/ServiceLatLong'
            - type: 'null'
        checkInAudio:
          anyOf:
            - type: string
            - type: 'null'
        checkInAudioTranscription:
          anyOf:
            - type: string
            - type: 'null'
        checkOutByEmail:
          anyOf:
            - type: string
            - type: 'null'
        checkOutByContact:
          anyOf:
            - $ref: '#/components/schemas/Contact'
            - type: 'null'
        checkOutStatus:
          anyOf:
            - type: string
            - type: 'null'
        checkOutTime:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        checkOutNotes:
          anyOf:
            - type: string
            - type: 'null'
        checkOutImages:
          type: array
          items:
            type: string
        checkOutGeoLocation:
          anyOf:
            - $ref: '#/components/schemas/ServiceLatLong'
            - type: 'null'
        numberOfTechs:
          type: integer
        workOrderId:
          type: integer
          format: int64
        locationId:
          type: integer
          format: int64
        buyerFacilityId:
          type: integer
        buyerCompanyId:
          type: integer
        createdAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        updatedAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        isDeleted:
          type: boolean
        deletedAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
      required:
        - supplierFacilityId
        - numberOfTechs
        - workOrderId
        - locationId
        - buyerFacilityId
        - buyerCompanyId
        - isDeleted
      description: >-
        Service call. Captioned-image and audio-transcription companion fields
        omitted here.
      additionalProperties: true
    FileDetails:
      type: object
      properties:
        fileName:
          type: string
          description: Original file name (non-ASCII characters removed on upload)
        fileId:
          type: string
          description: Server-assigned file identifier; use with the file download endpoint
      required:
        - fileName
        - fileId
      description: >-
        Uploaded-file reference, as returned by the file upload endpoint and
        used in all attachment fields.
    StockLocation:
      type: object
      properties:
        id:
          anyOf:
            - type: integer
              format: int64
            - type: 'null'
        warehouseId:
          anyOf:
            - type: string
            - type: 'null'
        name:
          type: string
        images:
          type: array
          items:
            type: string
        description:
          anyOf:
            - type: string
            - type: 'null'
        isMobile:
          type: boolean
        address:
          anyOf:
            - $ref: '#/components/schemas/Address'
            - type: 'null'
        latitude:
          anyOf:
            - type: number
            - type: 'null'
        longitude:
          anyOf:
            - type: number
            - type: 'null'
        timeZoneId:
          anyOf:
            - type: string
            - type: 'null'
        locationId:
          anyOf:
            - type: integer
              format: int64
            - type: 'null'
        supplierFacilityId:
          type: integer
        supplierCompanyId:
          type: integer
        burdenRates:
          anyOf:
            - type: array
              items:
                type: object
                properties:
                  year:
                    type: integer
                  rate:
                    type: string
                required:
                  - year
                  - rate
            - type: 'null'
        taxRates:
          anyOf:
            - type: array
              items:
                type: object
                properties:
                  year:
                    type: integer
                  rate:
                    type: string
                required:
                  - year
                  - rate
            - type: 'null'
        purchaseOrderPrefix:
          anyOf:
            - type: string
            - type: 'null'
        isActive:
          type: boolean
        glCode:
          anyOf:
            - type: string
            - type: 'null'
        allowEquipmentForwarding:
          type: boolean
        createdAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        updatedAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
        isDeleted:
          type: boolean
        deletedAt:
          anyOf:
            - type: string
              format: date-time
              description: ISO 8601 date-time
            - type: 'null'
      required:
        - name
        - isMobile
        - supplierFacilityId
        - supplierCompanyId
        - isActive
        - allowEquipmentForwarding
        - isDeleted
      additionalProperties: true
    ServiceLatLong:
      type: object
      properties:
        lat:
          type: string
        long:
          type: string
      required:
        - lat
        - long
    Address:
      type: object
      properties: {}
      description: Postal address object (shape abridged - model source not available).
      additionalProperties: true
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-KEY
    OwKeyAuth:
      type: apiKey
      in: header
      name: OW-KEY
      description: >-
        OpenWrench shared secret. Required on every request alongside X-API-KEY;
        issued together with your API key.

````