components:
    headers:
        Deprecation:
            description: RFC 8594 deprecation indicator. Either the literal value `true` or an HTTP-date marking when the endpoint became deprecated. Present on every response from a deprecated endpoint until the sunset date.
            schema:
                type: string
        RetryAfter:
            description: Seconds to wait before retrying.
            schema:
                type: integer
        Sunset:
            description: RFC 8594 sunset date. HTTP-date marking when the endpoint will stop responding (200 series replaced by 410 Gone). Pairs with Deprecation; appears on every response from a deprecated endpoint.
            schema:
                format: http-date
                type: string
        WWWAuthenticate:
            description: RFC 7235 authentication challenge. Always `Bearer realm="trakrf-api"` on 401 responses.
            schema:
                type: string
        XRateLimitLimit:
            description: Steady-state requests/min for this API key.
            schema:
                type: integer
        XRateLimitRemaining:
            description: Requests remaining before throttling; bounded by X-RateLimit-Limit.
            schema:
                type: integer
        XRateLimitReset:
            description: Unix timestamp (seconds) when X-RateLimit-Remaining will next equal X-RateLimit-Limit.
            schema:
                type: integer
        XRequestId:
            description: Server-assigned request correlation identifier; mirrored as error.request_id in error envelopes and echoed in server logs. Quote this when filing support tickets.
            schema:
                type: string
    responses:
        Gone:
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ErrorResponse'
            description: Endpoint sunset. The endpoint was deprecated and has now passed its Sunset date; clients should migrate to the documented replacement (RFC 8594).
        MethodNotAllowed:
            content:
                application/json:
                    schema:
                        $ref: '#/components/schemas/ErrorResponse'
            description: Method not allowed
            headers:
                Allow:
                    description: Comma-separated list of HTTP methods supported on this resource (RFC 7231 §6.5.5).
                    schema:
                        type: string
                X-RateLimit-Limit:
                    $ref: '#/components/headers/XRateLimitLimit'
                X-RateLimit-Remaining:
                    $ref: '#/components/headers/XRateLimitRemaining'
                X-RateLimit-Reset:
                    $ref: '#/components/headers/XRateLimitReset'
                X-Request-Id:
                    $ref: '#/components/headers/XRequestId'
    schemas:
        AddAssetTagResponse:
            properties:
                data:
                    $ref: '#/components/schemas/Tag'
            required:
                - data
            type: object
        AddLocationTagResponse:
            properties:
                data:
                    $ref: '#/components/schemas/Tag'
            required:
                - data
            type: object
        AssetHistoryItem:
            properties:
                duration_seconds:
                    format: int32
                    nullable: true
                    type: integer
                event_observed_at:
                    example: "2025-04-29T12:34:56.000Z"
                    format: date-time
                    type: string
                location_external_key:
                    nullable: true
                    type: string
                location_id:
                    format: int64
                    nullable: true
                    type: integer
            required:
                - event_observed_at
                - location_id
                - location_external_key
                - duration_seconds
            type: object
        AssetHistoryResponse:
            properties:
                data:
                    items:
                        $ref: '#/components/schemas/AssetHistoryItem'
                    type: array
                limit:
                    example: 50
                    format: int32
                    type: integer
                offset:
                    example: 0
                    format: int32
                    type: integer
                total_count:
                    example: 100
                    format: int32
                    type: integer
            required:
                - data
                - limit
                - offset
                - total_count
            type: object
        AssetLocationItem:
            properties:
                asset_deleted_at:
                    example: "2025-04-29T12:34:56.000Z"
                    format: date-time
                    nullable: true
                    readOnly: true
                    type: string
                asset_external_key:
                    type: string
                asset_id:
                    format: int64
                    type: integer
                asset_last_seen:
                    example: "2025-04-29T12:34:56.000Z"
                    format: date-time
                    type: string
                location_external_key:
                    nullable: true
                    type: string
                location_id:
                    format: int64
                    nullable: true
                    type: integer
            required:
                - asset_id
                - asset_external_key
                - location_id
                - location_external_key
                - asset_last_seen
                - asset_deleted_at
            type: object
        AssetLocationsResponse:
            properties:
                data:
                    items:
                        $ref: '#/components/schemas/AssetLocationItem'
                    type: array
                limit:
                    example: 50
                    format: int32
                    type: integer
                offset:
                    example: 0
                    format: int32
                    type: integer
                total_count:
                    example: 100
                    format: int32
                    type: integer
            required:
                - data
                - limit
                - offset
                - total_count
            type: object
        AssetView:
            properties:
                created_at:
                    example: "2025-04-29T12:34:56.000Z"
                    format: date-time
                    readOnly: true
                    type: string
                deleted_at:
                    example: "2025-04-29T12:34:56.000Z"
                    format: date-time
                    nullable: true
                    readOnly: true
                    type: string
                description:
                    nullable: true
                    type: string
                external_key:
                    type: string
                id:
                    format: int64
                    readOnly: true
                    type: integer
                is_active:
                    type: boolean
                location_external_key:
                    nullable: true
                    type: string
                location_id:
                    format: int64
                    nullable: true
                    type: integer
                metadata:
                    additionalProperties: true
                    type: object
                name:
                    type: string
                tags:
                    description: Tags currently attached to this resource. Read-only on PATCH; mutate via POST /{resource}/{id}/tags and DELETE /{resource}/{id}/tags/{tag_id}.
                    items:
                        $ref: '#/components/schemas/Tag'
                    type: array
                updated_at:
                    example: "2025-04-29T12:34:56.000Z"
                    format: date-time
                    readOnly: true
                    type: string
                valid_from:
                    example: "2025-04-29T12:34:56.000Z"
                    format: date-time
                    type: string
                valid_to:
                    example: "2025-04-29T12:34:56.000Z"
                    format: date-time
                    nullable: true
                    type: string
            required:
                - id
                - external_key
                - name
                - description
                - location_id
                - location_external_key
                - metadata
                - is_active
                - valid_from
                - valid_to
                - created_at
                - updated_at
                - deleted_at
                - tags
            type: object
        BarcodeTag:
            description: "Polymorphic identifier attached to an asset or location, discriminated by `tag_type` into one of three variants: `RfidTag` (RFID transponder, `tag_type: rfid`), `BleTag` (Bluetooth Low Energy beacon, `tag_type: ble`), or `BarcodeTag` (1D/2D barcode, `tag_type: barcode`). The `/assets/{asset_id}/tags` and `/locations/{location_id}/tags` subresources accept and return all three variants; `tag_type` together with `value` form the natural key (a tag value is unique within its kind, not across kinds). \n\n`tag_type` is an **open** enumeration per the versioning policy — new variants may be added in additive minor revisions without a `/api/v2` cut. Integrators should treat unknown `tag_type` values as forward-compatible: pass the row through untouched rather than rejecting it. \n\n**Codegen note:** some strict-typed generators (e.g. datamodel-codegen for Pydantic) materialize the single-value `tag_type` constants on each variant as separate enum classes — `TagType`, `TagType2`, `TagType4` or similar. That is a generator artifact and not part of the contract; the wire shape is a single discriminated union with three variants today. Treat the generated classes as implementation detail."
            properties:
                id:
                    format: int64
                    readOnly: true
                    type: integer
                tag_type:
                    description: Discriminator for the polymorphic Tag resource. `rfid` denotes an RFID transponder, `ble` denotes a Bluetooth Low Energy beacon, and `barcode` denotes a 1D/2D barcode. Together with `value`, this forms the natural key — the same `value` may exist under different `tag_type`s without conflict.
                    enum:
                        - barcode
                    type: string
                value:
                    maxLength: 255
                    minLength: 1
                    pattern: ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$
                    type: string
            required:
                - id
                - tag_type
                - value
            type: object
        BarcodeTagRequest:
            additionalProperties: false
            description: "Polymorphic identifier attached to an asset or location, discriminated by `tag_type` into one of three variants: `RfidTag` (RFID transponder, `tag_type: rfid`), `BleTag` (Bluetooth Low Energy beacon, `tag_type: ble`), or `BarcodeTag` (1D/2D barcode, `tag_type: barcode`). The `/assets/{asset_id}/tags` and `/locations/{location_id}/tags` subresources accept and return all three variants; `tag_type` together with `value` form the natural key (a tag value is unique within its kind, not across kinds). \n\n`tag_type` is an **open** enumeration per the versioning policy — new variants may be added in additive minor revisions without a `/api/v2` cut. Integrators should treat unknown `tag_type` values as forward-compatible: pass the row through untouched rather than rejecting it. \n\n**Codegen note:** some strict-typed generators (e.g. datamodel-codegen for Pydantic) materialize the single-value `tag_type` constants on each variant as separate enum classes — `TagType`, `TagType2`, `TagType4` or similar. That is a generator artifact and not part of the contract; the wire shape is a single discriminated union with three variants today. Treat the generated classes as implementation detail."
            properties:
                tag_type:
                    description: Discriminator for the polymorphic Tag resource. `rfid` denotes an RFID transponder, `ble` denotes a Bluetooth Low Energy beacon, and `barcode` denotes a 1D/2D barcode. Together with `value`, this forms the natural key — the same `value` may exist under different `tag_type`s without conflict.
                    enum:
                        - barcode
                    type: string
                value:
                    maxLength: 255
                    minLength: 1
                    pattern: ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$
                    type: string
            required:
                - tag_type
                - value
            type: object
        BleTag:
            description: "Polymorphic identifier attached to an asset or location, discriminated by `tag_type` into one of three variants: `RfidTag` (RFID transponder, `tag_type: rfid`), `BleTag` (Bluetooth Low Energy beacon, `tag_type: ble`), or `BarcodeTag` (1D/2D barcode, `tag_type: barcode`). The `/assets/{asset_id}/tags` and `/locations/{location_id}/tags` subresources accept and return all three variants; `tag_type` together with `value` form the natural key (a tag value is unique within its kind, not across kinds). \n\n`tag_type` is an **open** enumeration per the versioning policy — new variants may be added in additive minor revisions without a `/api/v2` cut. Integrators should treat unknown `tag_type` values as forward-compatible: pass the row through untouched rather than rejecting it. \n\n**Codegen note:** some strict-typed generators (e.g. datamodel-codegen for Pydantic) materialize the single-value `tag_type` constants on each variant as separate enum classes — `TagType`, `TagType2`, `TagType4` or similar. That is a generator artifact and not part of the contract; the wire shape is a single discriminated union with three variants today. Treat the generated classes as implementation detail."
            properties:
                id:
                    format: int64
                    readOnly: true
                    type: integer
                tag_type:
                    description: Discriminator for the polymorphic Tag resource. `rfid` denotes an RFID transponder, `ble` denotes a Bluetooth Low Energy beacon, and `barcode` denotes a 1D/2D barcode. Together with `value`, this forms the natural key — the same `value` may exist under different `tag_type`s without conflict.
                    enum:
                        - ble
                    type: string
                value:
                    maxLength: 255
                    minLength: 1
                    pattern: ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$
                    type: string
            required:
                - id
                - tag_type
                - value
            type: object
        BleTagRequest:
            additionalProperties: false
            description: "Polymorphic identifier attached to an asset or location, discriminated by `tag_type` into one of three variants: `RfidTag` (RFID transponder, `tag_type: rfid`), `BleTag` (Bluetooth Low Energy beacon, `tag_type: ble`), or `BarcodeTag` (1D/2D barcode, `tag_type: barcode`). The `/assets/{asset_id}/tags` and `/locations/{location_id}/tags` subresources accept and return all three variants; `tag_type` together with `value` form the natural key (a tag value is unique within its kind, not across kinds). \n\n`tag_type` is an **open** enumeration per the versioning policy — new variants may be added in additive minor revisions without a `/api/v2` cut. Integrators should treat unknown `tag_type` values as forward-compatible: pass the row through untouched rather than rejecting it. \n\n**Codegen note:** some strict-typed generators (e.g. datamodel-codegen for Pydantic) materialize the single-value `tag_type` constants on each variant as separate enum classes — `TagType`, `TagType2`, `TagType4` or similar. That is a generator artifact and not part of the contract; the wire shape is a single discriminated union with three variants today. Treat the generated classes as implementation detail."
            properties:
                tag_type:
                    description: Discriminator for the polymorphic Tag resource. `rfid` denotes an RFID transponder, `ble` denotes a Bluetooth Low Energy beacon, and `barcode` denotes a 1D/2D barcode. Together with `value`, this forms the natural key — the same `value` may exist under different `tag_type`s without conflict.
                    enum:
                        - ble
                    type: string
                value:
                    maxLength: 255
                    minLength: 1
                    pattern: ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$
                    type: string
            required:
                - tag_type
                - value
            type: object
        CreateAssetResponse:
            properties:
                data:
                    $ref: '#/components/schemas/AssetView'
            required:
                - data
            type: object
        CreateAssetWithTagsRequest:
            additionalProperties: false
            properties:
                description:
                    example: Main warehouse forklift
                    maxLength: 1024
                    minLength: 1
                    nullable: true
                    pattern: ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$
                    type: string
                external_key:
                    description: |-
                        external_key is optional. Omit to receive a server-assigned key in the
                        format ASSET-NNNN (per-organization sequence). When supplied, must
                        satisfy the external_key_pattern.
                    example: forklift-3
                    maxLength: 255
                    minLength: 1
                    pattern: ^[A-Za-z0-9-]+$
                    type: string
                is_active:
                    example: true
                    type: boolean
                metadata:
                    additionalProperties: {}
                    type: object
                name:
                    example: Forklift 3
                    maxLength: 255
                    minLength: 1
                    pattern: ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$
                    type: string
                tags:
                    items:
                        $ref: '#/components/schemas/TagRequest'
                    nullable: true
                    type: array
                valid_from:
                    example: "2025-01-01T00:00:00Z"
                    format: date-time
                    type: string
                valid_to:
                    example: "2026-01-01T00:00:00Z"
                    format: date-time
                    nullable: true
                    type: string
            required:
                - name
            type: object
        CreateLocationResponse:
            properties:
                data:
                    $ref: '#/components/schemas/LocationView'
            required:
                - data
            type: object
        CreateLocationWithTagsRequest:
            additionalProperties: false
            not:
                required:
                    - parent_id
                    - parent_external_key
            properties:
                description:
                    example: Main warehouse location
                    maxLength: 1024
                    minLength: 1
                    nullable: true
                    pattern: ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$
                    type: string
                external_key:
                    description: |-
                        external_key is optional. Omit to receive a server-assigned key in the
                        format LOC-NNNN (per-organization sequence), parallel to assets'
                        ASSET-NNNN behavior. When supplied, must satisfy the external_key_pattern.
                    example: wh1
                    maxLength: 255
                    minLength: 1
                    pattern: ^[A-Za-z0-9-]+$
                    type: string
                is_active:
                    example: true
                    type: boolean
                name:
                    example: Warehouse 1
                    maxLength: 255
                    minLength: 1
                    pattern: ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$
                    type: string
                parent_external_key:
                    example: wh1
                    maxLength: 255
                    minLength: 1
                    nullable: true
                    pattern: ^[A-Za-z0-9-]+$
                    type: string
                parent_id:
                    example: 42
                    format: int64
                    minimum: 1
                    nullable: true
                    type: integer
                tags:
                    items:
                        $ref: '#/components/schemas/TagRequest'
                    nullable: true
                    type: array
                valid_from:
                    example: "2025-12-14T00:00:00Z"
                    format: date-time
                    type: string
                valid_to:
                    example: "2026-12-14T00:00:00Z"
                    format: date-time
                    nullable: true
                    type: string
            required:
                - name
            type: object
        ErrorResponse:
            description: TrakRF error envelope, modeled on RFC 7807 but not 7807-compliant. Fields are nested under `error.*` and content-type is `application/json` (not `application/problem+json`). Generated clients should branch on `error.type` and `error.title`, not `error.detail`. `error.title` is a stable, machine-readable summary that does not vary between calls for the same condition. `error.detail` is the specific, human-readable cause of this particular failure and may be empty when title alone fully describes the condition.
            properties:
                error:
                    properties:
                        detail:
                            description: Specific, human-readable cause of this particular failure. May be empty when title alone fully describes the condition. Do not branch on this value.
                            type: string
                        fields:
                            items:
                                $ref: '#/components/schemas/FieldError'
                            type: array
                        instance:
                            type: string
                        request_id:
                            type: string
                        status:
                            format: int32
                            type: integer
                        title:
                            description: Stable, machine-readable summary suitable for client-side branching. Does not vary between calls for the same condition.
                            type: string
                        type:
                            $ref: '#/components/schemas/ErrorType'
                    required:
                        - type
                        - title
                        - status
                        - detail
                        - instance
                        - request_id
                    type: object
            required:
                - error
            type: object
        ErrorType:
            description: Machine-readable error envelope discriminator. Pairs with `title` and `detail` to drive client-side branching on a stable token (per RFC 9457).
            enum:
                - validation_error
                - bad_request
                - unauthorized
                - forbidden
                - not_found
                - conflict
                - rate_limited
                - internal_error
                - method_not_allowed
                - unsupported_media_type
                - missing_org_context
            example: validation_error
            type: string
            x-extensible-enum: true
        FieldError:
            properties:
                code:
                    $ref: '#/components/schemas/FieldErrorCode'
                field:
                    type: string
                message:
                    type: string
                params:
                    additionalProperties: {}
                    type: object
            required:
                - field
                - code
                - message
            type: object
        FieldErrorCode:
            description: Machine-readable field-level validation error code. Identifies which constraint a request payload violated; pairs with the field path and human-readable message in a FieldError entry.
            enum:
                - required
                - invalid_value
                - unknown_field
                - too_short
                - too_long
                - too_small
                - too_large
                - fk_not_found
                - ambiguous_fields
                - read_only
            example: required
            type: string
            x-extensible-enum: true
        GetAssetResponse:
            properties:
                data:
                    $ref: '#/components/schemas/AssetView'
            required:
                - data
            type: object
        GetCurrentOrgResponse:
            properties:
                data:
                    $ref: '#/components/schemas/OrgView'
            required:
                - data
            type: object
        GetLocationResponse:
            properties:
                data:
                    $ref: '#/components/schemas/LocationView'
            required:
                - data
            type: object
        ListAssetsResponse:
            properties:
                data:
                    items:
                        $ref: '#/components/schemas/AssetView'
                    type: array
                limit:
                    example: 50
                    format: int32
                    type: integer
                offset:
                    example: 0
                    format: int32
                    type: integer
                total_count:
                    example: 100
                    format: int32
                    type: integer
            required:
                - data
                - limit
                - offset
                - total_count
            type: object
        ListLocationAncestorsResponse:
            properties:
                data:
                    items:
                        $ref: '#/components/schemas/LocationView'
                    type: array
                limit:
                    example: 50
                    format: int32
                    type: integer
                offset:
                    example: 0
                    format: int32
                    type: integer
                total_count:
                    example: 100
                    format: int32
                    type: integer
            required:
                - data
                - limit
                - offset
                - total_count
            type: object
        ListLocationChildrenResponse:
            properties:
                data:
                    items:
                        $ref: '#/components/schemas/LocationView'
                    type: array
                limit:
                    example: 50
                    format: int32
                    type: integer
                offset:
                    example: 0
                    format: int32
                    type: integer
                total_count:
                    example: 100
                    format: int32
                    type: integer
            required:
                - data
                - limit
                - offset
                - total_count
            type: object
        ListLocationDescendantsResponse:
            properties:
                data:
                    items:
                        $ref: '#/components/schemas/LocationView'
                    type: array
                limit:
                    example: 50
                    format: int32
                    type: integer
                offset:
                    example: 0
                    format: int32
                    type: integer
                total_count:
                    example: 100
                    format: int32
                    type: integer
            required:
                - data
                - limit
                - offset
                - total_count
            type: object
        ListLocationsResponse:
            properties:
                data:
                    items:
                        $ref: '#/components/schemas/LocationView'
                    type: array
                limit:
                    example: 50
                    format: int32
                    type: integer
                offset:
                    example: 0
                    format: int32
                    type: integer
                total_count:
                    example: 100
                    format: int32
                    type: integer
            required:
                - data
                - limit
                - offset
                - total_count
            type: object
        LocationView:
            properties:
                created_at:
                    example: "2025-04-29T12:34:56.000Z"
                    format: date-time
                    readOnly: true
                    type: string
                deleted_at:
                    example: "2025-04-29T12:34:56.000Z"
                    format: date-time
                    nullable: true
                    readOnly: true
                    type: string
                description:
                    nullable: true
                    type: string
                external_key:
                    type: string
                id:
                    format: int64
                    readOnly: true
                    type: integer
                is_active:
                    type: boolean
                name:
                    type: string
                parent_external_key:
                    nullable: true
                    type: string
                parent_id:
                    format: int64
                    nullable: true
                    type: integer
                tags:
                    description: Tags currently attached to this resource. Read-only on PATCH; mutate via POST /{resource}/{id}/tags and DELETE /{resource}/{id}/tags/{tag_id}.
                    items:
                        $ref: '#/components/schemas/Tag'
                    type: array
                updated_at:
                    example: "2025-04-29T12:34:56.000Z"
                    format: date-time
                    readOnly: true
                    type: string
                valid_from:
                    example: "2025-04-29T12:34:56.000Z"
                    format: date-time
                    type: string
                valid_to:
                    example: "2025-04-29T12:34:56.000Z"
                    format: date-time
                    nullable: true
                    type: string
            required:
                - id
                - external_key
                - name
                - description
                - parent_id
                - parent_external_key
                - is_active
                - valid_from
                - valid_to
                - created_at
                - updated_at
                - deleted_at
                - tags
            type: object
        OrgView:
            properties:
                api_key_id:
                    example: 550e8400-e29b-41d4-a716-446655440000
                    type: string
                id:
                    example: 42
                    format: int64
                    readOnly: true
                    type: integer
                name:
                    example: Acme Logistics
                    type: string
                scopes:
                    example:
                        - assets:read
                        - assets:write
                    items:
                        type: string
                    type: array
            required:
                - id
                - name
                - scopes
                - api_key_id
            type: object
        RenameAssetRequest:
            additionalProperties: false
            properties:
                external_key:
                    example: ASSET-0042
                    maxLength: 255
                    minLength: 1
                    pattern: ^[A-Za-z0-9-]+$
                    type: string
            required:
                - external_key
            type: object
        RenameAssetResponse:
            properties:
                data:
                    $ref: '#/components/schemas/AssetView'
                descendant_count_affected:
                    example: 0
                    format: int32
                    type: integer
            required:
                - data
                - descendant_count_affected
            type: object
        RenameLocationRequest:
            additionalProperties: false
            properties:
                external_key:
                    example: wh1
                    maxLength: 255
                    minLength: 1
                    pattern: ^[A-Za-z0-9-]+$
                    type: string
            required:
                - external_key
            type: object
        RenameLocationResponse:
            properties:
                data:
                    $ref: '#/components/schemas/LocationView'
                descendant_count_affected:
                    example: 7
                    format: int32
                    type: integer
            required:
                - data
                - descendant_count_affected
            type: object
        RfidTag:
            description: "Polymorphic identifier attached to an asset or location, discriminated by `tag_type` into one of three variants: `RfidTag` (RFID transponder, `tag_type: rfid`), `BleTag` (Bluetooth Low Energy beacon, `tag_type: ble`), or `BarcodeTag` (1D/2D barcode, `tag_type: barcode`). The `/assets/{asset_id}/tags` and `/locations/{location_id}/tags` subresources accept and return all three variants; `tag_type` together with `value` form the natural key (a tag value is unique within its kind, not across kinds). \n\n`tag_type` is an **open** enumeration per the versioning policy — new variants may be added in additive minor revisions without a `/api/v2` cut. Integrators should treat unknown `tag_type` values as forward-compatible: pass the row through untouched rather than rejecting it. \n\n**Codegen note:** some strict-typed generators (e.g. datamodel-codegen for Pydantic) materialize the single-value `tag_type` constants on each variant as separate enum classes — `TagType`, `TagType2`, `TagType4` or similar. That is a generator artifact and not part of the contract; the wire shape is a single discriminated union with three variants today. Treat the generated classes as implementation detail."
            properties:
                id:
                    format: int64
                    readOnly: true
                    type: integer
                tag_type:
                    description: Discriminator for the polymorphic Tag resource. `rfid` denotes an RFID transponder, `ble` denotes a Bluetooth Low Energy beacon, and `barcode` denotes a 1D/2D barcode. Together with `value`, this forms the natural key — the same `value` may exist under different `tag_type`s without conflict.
                    enum:
                        - rfid
                    type: string
                value:
                    maxLength: 255
                    minLength: 1
                    pattern: ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$
                    type: string
            required:
                - id
                - tag_type
                - value
            type: object
        RfidTagRequest:
            additionalProperties: false
            description: "Polymorphic identifier attached to an asset or location, discriminated by `tag_type` into one of three variants: `RfidTag` (RFID transponder, `tag_type: rfid`), `BleTag` (Bluetooth Low Energy beacon, `tag_type: ble`), or `BarcodeTag` (1D/2D barcode, `tag_type: barcode`). The `/assets/{asset_id}/tags` and `/locations/{location_id}/tags` subresources accept and return all three variants; `tag_type` together with `value` form the natural key (a tag value is unique within its kind, not across kinds). \n\n`tag_type` is an **open** enumeration per the versioning policy — new variants may be added in additive minor revisions without a `/api/v2` cut. Integrators should treat unknown `tag_type` values as forward-compatible: pass the row through untouched rather than rejecting it. \n\n**Codegen note:** some strict-typed generators (e.g. datamodel-codegen for Pydantic) materialize the single-value `tag_type` constants on each variant as separate enum classes — `TagType`, `TagType2`, `TagType4` or similar. That is a generator artifact and not part of the contract; the wire shape is a single discriminated union with three variants today. Treat the generated classes as implementation detail."
            properties:
                tag_type:
                    description: Discriminator for the polymorphic Tag resource. `rfid` denotes an RFID transponder, `ble` denotes a Bluetooth Low Energy beacon, and `barcode` denotes a 1D/2D barcode. Together with `value`, this forms the natural key — the same `value` may exist under different `tag_type`s without conflict.
                    enum:
                        - rfid
                    type: string
                value:
                    maxLength: 255
                    minLength: 1
                    pattern: ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$
                    type: string
            required:
                - tag_type
                - value
            type: object
        Tag:
            description: "Polymorphic identifier attached to an asset or location, discriminated by `tag_type` into one of three variants: `RfidTag` (RFID transponder, `tag_type: rfid`), `BleTag` (Bluetooth Low Energy beacon, `tag_type: ble`), or `BarcodeTag` (1D/2D barcode, `tag_type: barcode`). The `/assets/{asset_id}/tags` and `/locations/{location_id}/tags` subresources accept and return all three variants; `tag_type` together with `value` form the natural key (a tag value is unique within its kind, not across kinds). \n\n`tag_type` is an **open** enumeration per the versioning policy — new variants may be added in additive minor revisions without a `/api/v2` cut. Integrators should treat unknown `tag_type` values as forward-compatible: pass the row through untouched rather than rejecting it. \n\n**Codegen note:** some strict-typed generators (e.g. datamodel-codegen for Pydantic) materialize the single-value `tag_type` constants on each variant as separate enum classes — `TagType`, `TagType2`, `TagType4` or similar. That is a generator artifact and not part of the contract; the wire shape is a single discriminated union with three variants today. Treat the generated classes as implementation detail."
            discriminator:
                mapping:
                    barcode: '#/components/schemas/BarcodeTag'
                    ble: '#/components/schemas/BleTag'
                    rfid: '#/components/schemas/RfidTag'
                propertyName: tag_type
            oneOf:
                - $ref: '#/components/schemas/RfidTag'
                - $ref: '#/components/schemas/BleTag'
                - $ref: '#/components/schemas/BarcodeTag'
        TagRequest:
            description: "Polymorphic identifier attached to an asset or location, discriminated by `tag_type` into one of three variants: `RfidTag` (RFID transponder, `tag_type: rfid`), `BleTag` (Bluetooth Low Energy beacon, `tag_type: ble`), or `BarcodeTag` (1D/2D barcode, `tag_type: barcode`). The `/assets/{asset_id}/tags` and `/locations/{location_id}/tags` subresources accept and return all three variants; `tag_type` together with `value` form the natural key (a tag value is unique within its kind, not across kinds). \n\n`tag_type` is an **open** enumeration per the versioning policy — new variants may be added in additive minor revisions without a `/api/v2` cut. Integrators should treat unknown `tag_type` values as forward-compatible: pass the row through untouched rather than rejecting it. \n\n**Codegen note:** some strict-typed generators (e.g. datamodel-codegen for Pydantic) materialize the single-value `tag_type` constants on each variant as separate enum classes — `TagType`, `TagType2`, `TagType4` or similar. That is a generator artifact and not part of the contract; the wire shape is a single discriminated union with three variants today. Treat the generated classes as implementation detail."
            discriminator:
                mapping:
                    barcode: '#/components/schemas/BarcodeTagRequest'
                    ble: '#/components/schemas/BleTagRequest'
                    rfid: '#/components/schemas/RfidTagRequest'
                propertyName: tag_type
            oneOf:
                - $ref: '#/components/schemas/RfidTagRequest'
                - $ref: '#/components/schemas/BleTagRequest'
                - $ref: '#/components/schemas/BarcodeTagRequest'
        UpdateAssetRequest:
            properties:
                description:
                    example: Updated description
                    maxLength: 1024
                    minLength: 1
                    nullable: true
                    pattern: ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$
                    type: string
                is_active:
                    example: true
                    type: boolean
                metadata:
                    additionalProperties: {}
                    type: object
                name:
                    example: Forklift 3
                    maxLength: 255
                    minLength: 1
                    pattern: ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$
                    type: string
                valid_from:
                    example: "2025-01-01T00:00:00Z"
                    format: date-time
                    type: string
                valid_to:
                    example: "2026-01-01T00:00:00Z"
                    format: date-time
                    nullable: true
                    type: string
            type: object
        UpdateAssetResponse:
            properties:
                data:
                    $ref: '#/components/schemas/AssetView'
            required:
                - data
            type: object
        UpdateLocationRequest:
            properties:
                description:
                    example: Updated description
                    maxLength: 1024
                    minLength: 1
                    nullable: true
                    pattern: ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$
                    type: string
                is_active:
                    example: true
                    type: boolean
                name:
                    example: Warehouse 1
                    maxLength: 255
                    minLength: 1
                    pattern: ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$
                    type: string
                parent_external_key:
                    example: wh1
                    maxLength: 255
                    minLength: 1
                    nullable: true
                    pattern: ^[A-Za-z0-9-]+$
                    type: string
                parent_id:
                    example: 42
                    format: int64
                    minimum: 1
                    nullable: true
                    type: integer
                valid_from:
                    example: "2025-12-14T00:00:00Z"
                    format: date-time
                    type: string
                valid_to:
                    example: "2026-12-14T00:00:00Z"
                    format: date-time
                    nullable: true
                    type: string
            type: object
        UpdateLocationResponse:
            properties:
                data:
                    $ref: '#/components/schemas/LocationView'
            required:
                - data
            type: object
    securitySchemes:
        BearerAuth:
            bearerFormat: JWT
            description: 'TrakRF API key (JWT). Format: "Bearer <jwt>". Mint keys from the API Keys section of your TrakRF account. Some OpenAPI generators (e.g. openapi-fetch) do not auto-attach the Authorization header from this scheme — set it manually if your generated client does not.'
            scheme: bearer
            type: http
info:
    contact:
        email: support@trakrf.id
        name: TrakRF Support
        url: https://docs.trakrf.id/
    description: |-
        TrakRF public REST API. See /api for the customer-facing reference.

        Spec available as YAML (/api/openapi.yaml) and JSON (/api/openapi.json).

        HTTP method coverage (HEAD, OPTIONS, 405 / Allow): /api/http-method-coverage

        Surrogate ID width: declared `format: int64` on the wire so SDK regeneration does not break when the ID namespace eventually outgrows int32. Service-side ID generation stays within int32 (2^31-1) during v1; values above that bound are rejected with 400 validation_error / `too_large`. The wider wire type is a long-horizon contract, not a claim that current values exceed int32.

        Nullable field interpretation: OpenAPI 3.0's `nullable: true` keyword is interpreted differently across codegen targets. Verified-working: `openapi-typescript@7.x` emits `string | null` correctly, and the `openapi-generator-cli` python target emits `Optional[StrictStr]` correctly — both round-trip CRUD against null-bearing responses unmodified. Known-broken: `datamodel-codegen@0.57.0` emits `nullable: true` read-shape fields as non-Optional required fields, so Pydantic validation fails on every nullable field that is actually null. Integrators using `datamodel-codegen` should either switch to the `openapi-generator-cli` python target or apply `--use-annotated --use-union-operator` with a custom post-processing pass.
    license:
        name: Business Source License 1.1
        url: https://github.com/trakrf/platform/blob/main/LICENSE
    title: TrakRF API
    version: 1.0.0
openapi: 3.0.3
paths:
    /api/v1/assets:
        get:
            description: |-
                **Required scope:** `assets:read`

                Paginated assets list with natural-key filters, sort, and substring search.

                Default scope returns currently-effective assets only — rows whose `valid_from` is in the past AND whose `valid_to` is null or in the future. The `is_active` filter is independent of temporal validity; omit it to include both active and inactive rows within the effective window, or pass `?is_active=true`/`false` to filter further.
            operationId: listAssets
            parameters:
                - description: max 200
                  in: query
                  name: limit
                  schema:
                    default: 50
                    maximum: 200
                    minimum: 1
                    type: integer
                - description: min 0
                  in: query
                  name: offset
                  schema:
                    default: 0
                    maximum: 2147483647
                    minimum: 0
                    type: integer
                - description: filter by current location id (canonical, may repeat); mutually exclusive with location_external_key (400 ambiguous_fields if both supplied)
                  explode: true
                  in: query
                  name: location_id
                  schema:
                    default: []
                    items:
                        format: int64
                        maximum: 2147483647
                        minimum: 1
                        type: integer
                    type: array
                  style: form
                - description: filter by current location external_key (may repeat); mutually exclusive with location_id (400 ambiguous_fields if both supplied)
                  explode: true
                  in: query
                  name: location_external_key
                  schema:
                    default: []
                    items:
                        pattern: ^[A-Za-z0-9-]+$
                        type: string
                    type: array
                  style: form
                - description: filter by asset external_key, equality match (may repeat for any-of)
                  explode: true
                  in: query
                  name: external_key
                  schema:
                    default: []
                    items:
                        pattern: ^[A-Za-z0-9-]+$
                        type: string
                    type: array
                  style: form
                - description: filter by active flag
                  in: query
                  name: is_active
                  schema:
                    type: boolean
                - description: when true, include soft-deleted rows in the response. deleted_at is populated for those rows. Orthogonal to is_active.
                  in: query
                  name: include_deleted
                  schema:
                    default: false
                    type: boolean
                - description: substring search (case-insensitive) on name, external_key, description, and active tag values
                  in: query
                  name: q
                  schema:
                    pattern: ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$
                    type: string
                - description: comma-separated; prefix '-' for DESC
                  explode: false
                  in: query
                  name: sort
                  schema:
                    default: ""
                    pattern: ^(|(?:external_key|-external_key|name|-name|created_at|-created_at|updated_at|-updated_at)(?:,(?:external_key|-external_key|name|-name|created_at|-created_at|updated_at|-updated_at))*)$
                    type: string
                  style: form
            responses:
                "200":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ListAssetsResponse'
                    description: OK
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Bad Request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Not Found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Internal Server Error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: List assets
            tags:
                - assets
            x-required-scopes:
                - assets:read
        post:
            description: |-
                **Required scope:** `assets:write`

                Create a new asset record, optionally with one or more tags (RFID, BLE, barcode).

                The `external_key` field is optional. Provide a value from your system of record
                (ERP, WMS, asset management) for natural-key joins, or omit it to receive a
                server-assigned external_key in the format `ASSET-NNNN` (per-organization sequence).
                A caller-supplied external_key that collides with an existing asset returns 409.

                Returns the created asset with its assigned tags. The Location response header contains the canonical URL.
            operationId: createAsset
            requestBody:
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/CreateAssetWithTagsRequest'
                description: Asset to create with optional tags
                required: true
                x-originalParamName: request
            responses:
                "201":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/CreateAssetResponse'
                    description: Created
                    headers:
                        Location:
                            description: Canonical URL of the created resource
                            schema:
                                type: string
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: bad_request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: not_found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "409":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: conflict
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "415":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unsupported_media_type
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: internal_error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: Create an asset
            tags:
                - assets
            x-required-scopes:
                - assets:write
    /api/v1/assets/{asset_id}:
        delete:
            description: |-
                **Required scope:** `assets:write`

                Delete an asset by its canonical id. The asset is removed from all subsequent queries and its external_key becomes immediately available for reuse. Returns 204 on success, 404 if the asset does not exist or has already been deleted.
            operationId: deleteAsset
            parameters:
                - description: Asset id (canonical)
                  in: path
                  name: asset_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
            responses:
                "204":
                    description: deleted
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: bad_request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: not_found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: internal_error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: Delete an asset
            tags:
                - assets
            x-required-scopes:
                - assets:write
        get:
            description: |-
                **Required scope:** `assets:read`

                Retrieve an asset by its canonical id. Returns 404 if the asset does not exist.

                Path-addressed retrieval bypasses the temporal-validity filter applied on list endpoints — any non-deleted asset is returned regardless of its `valid_from` / `valid_to` values. Use this endpoint when you have an id and need the row even if its effective window has elapsed.
            operationId: getAsset
            parameters:
                - description: Asset id (canonical)
                  in: path
                  name: asset_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
            responses:
                "200":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/GetAssetResponse'
                    description: OK
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Bad Request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Not Found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: Get asset by canonical id
            tags:
                - assets
            x-required-scopes:
                - assets:read
        patch:
            description: |-
                **Required scope:** `assets:write`

                Apply a JSON Merge Patch (RFC 7396) to an asset. Only fields included in the request body are changed; fields set to `null` clear the corresponding nullable column. Omitted fields are left unchanged. An empty body (`{}`) is a no-op and returns the current resource unchanged. Read-only fields are uniformly governed by the accept-if-matches, reject-if-differs rule: a value matching the current resource state is silently normalized out (so a verbatim GET → PATCH round-trip succeeds without manual scrubbing), and a differing value returns 400 with `code: read_only`. This applies to the server-managed surrogate id + timestamps (`id`, `created_at`, `updated_at`, `deleted_at`), the `tags` collection, and the natural-key reference fields (`external_key`, `location_id`, `location_external_key`). Mutate `external_key` via POST /assets/{asset_id}/rename; asset location is collected through scan event ingestion (fixed-reader MQTT pipeline or handheld UI submission) and is not directly settable through the public API; mutate `tags` via POST /assets/{asset_id}/tags and DELETE /assets/{asset_id}/tags/{tag_id}.
            operationId: updateAsset
            parameters:
                - description: Asset id (canonical)
                  in: path
                  name: asset_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
            requestBody:
                content:
                    application/merge-patch+json:
                        schema:
                            $ref: '#/components/schemas/UpdateAssetRequest'
                description: Fields to merge-patch
                required: true
                x-originalParamName: request
            responses:
                "200":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/UpdateAssetResponse'
                    description: OK
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: bad_request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: not_found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "409":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: conflict
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "415":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unsupported_media_type
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: internal_error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: Update an asset
            tags:
                - assets
            x-required-scopes:
                - assets:write
    /api/v1/assets/{asset_id}/history:
        get:
            description: |-
                **Required scope:** `tracking:read`

                Location history for an asset identified by its canonical id.

                The asset existence check follows path-addressed semantics — the asset is returned even if its `valid_to` has elapsed. Each history row's location reference applies the temporal-validity predicate, so an event referencing a location whose effective window is past surfaces with null `location_external_key`.
            operationId: getAssetHistory
            parameters:
                - description: Asset id (canonical)
                  in: path
                  name: asset_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
                - description: max 200
                  in: query
                  name: limit
                  schema:
                    default: 50
                    maximum: 200
                    minimum: 1
                    type: integer
                - description: min 0
                  in: query
                  name: offset
                  schema:
                    default: 0
                    maximum: 2147483647
                    minimum: 0
                    type: integer
                - description: RFC 3339 start timestamp
                  in: query
                  name: from
                  schema:
                    example: "2025-04-29T12:34:56.000Z"
                    format: date-time
                    type: string
                - description: RFC 3339 end timestamp
                  in: query
                  name: to
                  schema:
                    example: "2025-04-29T12:34:56.000Z"
                    format: date-time
                    type: string
                - description: comma-separated; prefix '-' for DESC
                  explode: false
                  in: query
                  name: sort
                  schema:
                    default: ""
                    pattern: ^(|(?:event_observed_at|-event_observed_at)(?:,(?:event_observed_at|-event_observed_at))*)$
                    type: string
                  style: form
            responses:
                "200":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AssetHistoryResponse'
                    description: OK
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Bad Request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Not Found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Internal Server Error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: Asset movement history
            tags:
                - assets
            x-required-scopes:
                - tracking:read
    /api/v1/assets/{asset_id}/rename:
        post:
            description: |-
                **Required scope:** `assets:write`

                Mutate the asset's `external_key` (natural / join key). This operation is **destructive to downstream joins**: any external system that has cached or indexed records on the old `external_key` will silently disconnect. Prefer a coordinated cutover with downstream consumers.

                `external_key` is immutable via PATCH; this operation is the only way to change it. Distinct from a regular PATCH in audit logs (different URL surface).

                The response includes `descendant_count_affected` for shape uniformity with the location rename verb. Assets have no hierarchy, so this value is always 0; it is emitted to save typed-client consumers from branching on rename verb.
            operationId: renameAsset
            parameters:
                - description: Asset id (canonical)
                  in: path
                  name: asset_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
            requestBody:
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/RenameAssetRequest'
                description: New external_key
                required: true
                x-originalParamName: request
            responses:
                "200":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/RenameAssetResponse'
                    description: OK
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: bad_request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: not_found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "409":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: conflict
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "415":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unsupported_media_type
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: internal_error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: Rename an asset (mutate external_key)
            tags:
                - assets
            x-required-scopes:
                - assets:write
    /api/v1/assets/{asset_id}/tags:
        post:
            description: |-
                **Required scope:** `assets:write`

                Attach a tag (RFID EPC, BLE beacon ID, barcode, etc.) to an existing asset.
                The tag must be unique within the organization.
            operationId: addAssetTag
            parameters:
                - description: Asset id (canonical)
                  in: path
                  name: asset_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
            requestBody:
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/TagRequest'
                description: Tag to attach
                required: true
                x-originalParamName: request
            responses:
                "201":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AddAssetTagResponse'
                    description: tag attached
                    headers:
                        Location:
                            description: Canonical URL of the created tag
                            schema:
                                type: string
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: bad_request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: not_found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "409":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: conflict
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "415":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unsupported_media_type
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: internal_error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: Add a tag to an asset
            tags:
                - assets
            x-required-scopes:
                - assets:write
    /api/v1/assets/{asset_id}/tags/{tag_id}:
        delete:
            description: |-
                **Required scope:** `assets:write`

                Detach a tag from an asset by its tag record id.
                First successful removal returns 204; repeated calls return 404 — consistent with top-level resource DELETE semantics. The cross-asset / cross-org case (a tag that exists but is not attached to this asset, or belongs to a different org) also surfaces as 404.
            operationId: removeAssetTag
            parameters:
                - description: Asset id (canonical)
                  in: path
                  name: asset_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
                - description: Tag id
                  in: path
                  name: tag_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
            responses:
                "204":
                    description: deleted
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: bad_request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: not_found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: internal_error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: Remove a tag from an asset
            tags:
                - assets
            x-required-scopes:
                - assets:write
    /api/v1/locations:
        get:
            description: |-
                **Required scope:** `locations:read`

                Paginated locations list with natural-key filters, sort, and substring search.

                Default scope returns currently-effective locations only — rows whose `valid_from` is in the past AND whose `valid_to` is null or in the future. The `is_active` filter is independent of temporal validity; omit it to include both active and inactive rows within the effective window, or pass `?is_active=true`/`false` to filter further.
            operationId: listLocations
            parameters:
                - description: max 200
                  in: query
                  name: limit
                  schema:
                    default: 50
                    maximum: 200
                    minimum: 1
                    type: integer
                - description: min 0
                  in: query
                  name: offset
                  schema:
                    default: 0
                    maximum: 2147483647
                    minimum: 0
                    type: integer
                - description: filter by parent id (canonical, may repeat); mutually exclusive with parent_external_key (400 ambiguous_fields if both supplied)
                  explode: true
                  in: query
                  name: parent_id
                  schema:
                    default: []
                    items:
                        format: int64
                        maximum: 2147483647
                        minimum: 1
                        type: integer
                    type: array
                  style: form
                - description: filter by parent's external_key (may repeat); mutually exclusive with parent_id (400 ambiguous_fields if both supplied)
                  explode: true
                  in: query
                  name: parent_external_key
                  schema:
                    default: []
                    items:
                        pattern: ^[A-Za-z0-9-]+$
                        type: string
                    type: array
                  style: form
                - description: filter by location external_key, equality match (may repeat for any-of)
                  explode: true
                  in: query
                  name: external_key
                  schema:
                    default: []
                    items:
                        pattern: ^[A-Za-z0-9-]+$
                        type: string
                    type: array
                  style: form
                - description: filter by active flag
                  in: query
                  name: is_active
                  schema:
                    type: boolean
                - description: when true, include soft-deleted rows in the response. deleted_at is populated for those rows. Orthogonal to is_active.
                  in: query
                  name: include_deleted
                  schema:
                    default: false
                    type: boolean
                - description: substring search (case-insensitive) on name, external_key, description, and active tag values
                  in: query
                  name: q
                  schema:
                    pattern: ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$
                    type: string
                - description: comma-separated, prefix '-' for DESC
                  explode: false
                  in: query
                  name: sort
                  schema:
                    default: ""
                    pattern: ^(|(?:external_key|-external_key|name|-name|created_at|-created_at)(?:,(?:external_key|-external_key|name|-name|created_at|-created_at))*)$
                    type: string
                  style: form
            responses:
                "200":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ListLocationsResponse'
                    description: OK
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: bad_request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: not_found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: internal_error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: List locations
            tags:
                - locations
            x-required-scopes:
                - locations:read
        post:
            description: |-
                **Required scope:** `locations:write`

                Create a new location in the hierarchy, optionally with one or more tags.
                Set parent_id (canonical) or parent_external_key (alternate) to nest under an existing parent.

                The `external_key` field is optional. Provide a value from your system of record
                (ERP, WMS, layout/plan) for natural-key joins, or omit it to receive a
                server-assigned external_key in the format `LOC-NNNN` (per-organization sequence).
                A caller-supplied external_key that collides with an existing location returns 409.
            operationId: createLocation
            requestBody:
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/CreateLocationWithTagsRequest'
                description: Location to create with optional tags
                required: true
                x-originalParamName: request
            responses:
                "201":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/CreateLocationResponse'
                    description: Created
                    headers:
                        Location:
                            description: Canonical URL of the created resource
                            schema:
                                type: string
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: bad_request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: not_found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "409":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: conflict
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "415":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unsupported_media_type
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: internal_error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: Create a location
            tags:
                - locations
            x-required-scopes:
                - locations:write
    /api/v1/locations/{location_id}:
        delete:
            description: |-
                **Required scope:** `locations:write`

                Delete a location by its ID. Returns 204 on success, 404 if the location does not exist or has already been deleted, and 409 if the location has descendant locations or assets placed directly at it. Descendants must be reassigned or removed and placed assets must be moved or removed before their parent location can be deleted; bulk cascade is not supported.
            operationId: deleteLocation
            parameters:
                - description: Location ID
                  in: path
                  name: location_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
            responses:
                "204":
                    description: deleted
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: bad_request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: not_found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "409":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: conflict — has descendants or placed assets
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: internal_error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: Delete location
            tags:
                - locations
            x-required-scopes:
                - locations:write
        get:
            description: |-
                **Required scope:** `locations:read`

                Retrieve a location by its canonical ID. Returns 404 if not found.

                Path-addressed retrieval bypasses the temporal-validity filter applied on list endpoints — any non-deleted location is returned regardless of its `valid_from` / `valid_to` values. Use this endpoint when you have an id and need the row even if its effective window has elapsed.
            operationId: getLocation
            parameters:
                - description: Location ID
                  in: path
                  name: location_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
            responses:
                "200":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/GetLocationResponse'
                    description: OK
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Bad Request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Not Found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Too Many Requests
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: Get location by ID
            tags:
                - locations
            x-required-scopes:
                - locations:read
        patch:
            description: |-
                **Required scope:** `locations:write`

                Apply a JSON Merge Patch (RFC 7396) to a location. Only fields included in the request body are changed; fields set to `null` clear the corresponding nullable column. Omitted fields are left unchanged. An empty body (`{}`) is a no-op and returns the current resource unchanged. Read-only fields are uniformly governed by the accept-if-matches, reject-if-differs rule: a value matching the current resource state is silently normalized out (so a verbatim GET → PATCH round-trip succeeds without manual scrubbing), and a differing value returns 400 with `code: read_only`. This applies to the server-managed surrogate id + timestamps (`id`, `created_at`, `updated_at`, `deleted_at`), the `tags` collection, and the `external_key` natural key. To re-parent, send EITHER `parent_id` (surrogate) OR `parent_external_key` (natural key); both forms accept `null` to clear the FK, and supplying both in the same body returns 400 `ambiguous_fields` (symmetric with CreateLocationRequest). Mutate `external_key` via POST /locations/{location_id}/rename; mutate `tags` via POST /locations/{location_id}/tags and DELETE /locations/{location_id}/tags/{tag_id}.
            operationId: updateLocation
            parameters:
                - description: Location ID
                  in: path
                  name: location_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
            requestBody:
                content:
                    application/merge-patch+json:
                        schema:
                            $ref: '#/components/schemas/UpdateLocationRequest'
                description: Fields to merge-patch
                required: true
                x-originalParamName: request
            responses:
                "200":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/UpdateLocationResponse'
                    description: OK
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: bad_request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: not_found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "409":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: conflict
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "415":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unsupported_media_type
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: internal_error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: Update a location
            tags:
                - locations
            x-required-scopes:
                - locations:write
    /api/v1/locations/{location_id}/ancestors:
        get:
            description: |-
                **Required scope:** `locations:read`

                Sort order is fixed: ancestors are returned root first (walking up the `parent_id` chain), with `id` ascending as a deterministic tiebreaker. No `sort` query parameter is exposed because the natural order toward the root is the only meaningful order for this list.
            operationId: listLocationAncestors
            parameters:
                - description: Location ID
                  in: path
                  name: location_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
                - description: max 200
                  in: query
                  name: limit
                  schema:
                    default: 50
                    maximum: 200
                    minimum: 1
                    type: integer
                - description: min 0
                  in: query
                  name: offset
                  schema:
                    default: 0
                    maximum: 2147483647
                    minimum: 0
                    type: integer
            responses:
                "200":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ListLocationAncestorsResponse'
                    description: OK
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: bad_request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: not_found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: internal_error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: List location ancestors
            tags:
                - locations
            x-required-scopes:
                - locations:read
    /api/v1/locations/{location_id}/children:
        get:
            description: |-
                **Required scope:** `locations:read`

                Sort order is fixed: immediate children are returned ordered alphabetically by `name` ascending, with `id` ascending as a deterministic tiebreaker when sibling names collide. No `sort` query parameter is exposed because alphabetical-by-name is the only meaningful order for a single level of siblings.
            operationId: listLocationChildren
            parameters:
                - description: Location ID
                  in: path
                  name: location_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
                - description: max 200
                  in: query
                  name: limit
                  schema:
                    default: 50
                    maximum: 200
                    minimum: 1
                    type: integer
                - description: min 0
                  in: query
                  name: offset
                  schema:
                    default: 0
                    maximum: 2147483647
                    minimum: 0
                    type: integer
            responses:
                "200":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ListLocationChildrenResponse'
                    description: OK
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: bad_request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: not_found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: internal_error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: List location children
            tags:
                - locations
            x-required-scopes:
                - locations:read
    /api/v1/locations/{location_id}/descendants:
        get:
            description: |-
                **Required scope:** `locations:read`

                Sort order is fixed: descendants are returned in depth-first tree order (each level sorted by lowercased `external_key`), with `id` ascending as a deterministic tiebreaker. No `sort` query parameter is exposed because the depth-first tree walk is the only meaningful order for this list.
            operationId: listLocationDescendants
            parameters:
                - description: Location ID
                  in: path
                  name: location_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
                - description: max 200
                  in: query
                  name: limit
                  schema:
                    default: 50
                    maximum: 200
                    minimum: 1
                    type: integer
                - description: min 0
                  in: query
                  name: offset
                  schema:
                    default: 0
                    maximum: 2147483647
                    minimum: 0
                    type: integer
            responses:
                "200":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ListLocationDescendantsResponse'
                    description: OK
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: bad_request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: not_found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: internal_error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: List location descendants
            tags:
                - locations
            x-required-scopes:
                - locations:read
    /api/v1/locations/{location_id}/rename:
        post:
            description: |-
                **Required scope:** `locations:write`

                Mutate the location's `external_key`. This operation is **destructive to downstream joins** because consumers of the natural key must re-resolve it. Only this row's `external_key` changes on the server; descendants are not modified.

                The response includes `descendant_count_affected` (the live descendant count reachable through `parent_id`) so an integrator can decide whether to refresh derived natural-key joins for the subtree. `external_key` is immutable via PATCH; this operation is the only way to change it. Distinct from a regular PATCH in audit logs (different URL surface).
            operationId: renameLocation
            parameters:
                - description: Location ID
                  in: path
                  name: location_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
            requestBody:
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/RenameLocationRequest'
                description: New external_key
                required: true
                x-originalParamName: request
            responses:
                "200":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/RenameLocationResponse'
                    description: OK
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: bad_request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: not_found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "409":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: conflict
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "415":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unsupported_media_type
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: internal_error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: Rename a location (mutate external_key)
            tags:
                - locations
            x-required-scopes:
                - locations:write
    /api/v1/locations/{location_id}/tags:
        post:
            description: '**Required scope:** `locations:write`'
            operationId: addLocationTag
            parameters:
                - description: Location ID
                  in: path
                  name: location_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
            requestBody:
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/TagRequest'
                description: Tag to attach
                required: true
                x-originalParamName: request
            responses:
                "201":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AddLocationTagResponse'
                    description: tag attached
                    headers:
                        Location:
                            description: Canonical URL of the created tag
                            schema:
                                type: string
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: bad_request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: not_found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "409":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: conflict
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "415":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unsupported_media_type
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: internal_error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: Add a tag to a location
            tags:
                - locations
            x-required-scopes:
                - locations:write
    /api/v1/locations/{location_id}/tags/{tag_id}:
        delete:
            description: |-
                **Required scope:** `locations:write`

                Detach a tag from a location by its tag record id.
                First successful removal returns 204; repeated calls return 404 — consistent with top-level resource DELETE semantics. The cross-location / cross-org case (a tag that exists but is not attached to this location, or belongs to a different org) also surfaces as 404.
            operationId: removeLocationTag
            parameters:
                - description: Location ID
                  in: path
                  name: location_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
                - description: Tag ID
                  in: path
                  name: tag_id
                  required: true
                  schema:
                    format: int64
                    maximum: 2147483647
                    minimum: 1
                    type: integer
            responses:
                "204":
                    description: deleted
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: bad_request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: not_found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: internal_error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: Remove a tag from a location
            tags:
                - locations
            x-required-scopes:
                - locations:write
    /api/v1/orgs/me:
        get:
            description: Returns the organization scoped by the API key presented in Authorization. Intended as a lightweight health-check primitive for integrators verifying a key works end-to-end.
            operationId: getCurrentOrg
            responses:
                "200":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/GetCurrentOrgResponse'
                    description: OK
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: bad_request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: not_found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "422":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: missing_org_context — the API key authenticated but its org no longer exists
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Internal server error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: Get the org associated with the authenticated API key
            tags:
                - orgs
            x-required-scopes: []
    /api/v1/reports/asset-locations:
        get:
            description: |-
                **Required scope:** `tracking:read`

                Snapshot of each asset's most recent location, filterable by either side of the join. Filter by location (`location_id` / `location_external_key`) to retrieve everything currently at a place; filter by asset (`asset_id` / `asset_external_key`, repeatable) to resolve a batch of assets from a master system to their current locations in one round-trip. Within each pair the surrogate and natural-key forms are mutually exclusive (400 `ambiguous_fields` if both are supplied); the asset and location filter pairs are independent and intersect when combined. Because this view is derived from immutable scan history, it can resolve references for assets that have since been deleted. By default those rows are excluded; pass `include_deleted=true` to include them, and check `asset_deleted_at` to distinguish deleted from live.

                Temporal validity is applied to both joined entities. Assets whose effective window is past or future are excluded entirely. Locations whose effective window is past or future surface with null `location_id` / `location_external_key` while the parent asset row remains visible. Soft-deleted locations are projected the same way here — null on the report row — even though the identifier still lives on the location row; reports endpoints intentionally hide tombstoned anchor points from scan-derived summaries. Use the locations endpoint with `include_deleted=true` to retrieve the underlying identifier.
            operationId: getAssetLocations
            parameters:
                - description: max 200
                  in: query
                  name: limit
                  schema:
                    default: 50
                    maximum: 200
                    minimum: 1
                    type: integer
                - description: min 0
                  in: query
                  name: offset
                  schema:
                    default: 0
                    maximum: 2147483647
                    minimum: 0
                    type: integer
                - description: filter by location id (canonical, may repeat); mutually exclusive with location_external_key (400 ambiguous_fields if both supplied)
                  explode: true
                  in: query
                  name: location_id
                  schema:
                    default: []
                    items:
                        format: int64
                        maximum: 2147483647
                        minimum: 1
                        type: integer
                    type: array
                  style: form
                - description: filter by location external_key (may repeat); mutually exclusive with location_id (400 ambiguous_fields if both supplied)
                  explode: true
                  in: query
                  name: location_external_key
                  schema:
                    default: []
                    items:
                        pattern: ^[A-Za-z0-9-]+$
                        type: string
                    type: array
                  style: form
                - description: filter by asset id (canonical, may repeat); mutually exclusive with asset_external_key (400 ambiguous_fields if both supplied)
                  explode: true
                  in: query
                  name: asset_id
                  schema:
                    default: []
                    items:
                        format: int64
                        maximum: 2147483647
                        minimum: 1
                        type: integer
                    type: array
                  style: form
                - description: filter by asset external_key (may repeat); mutually exclusive with asset_id (400 ambiguous_fields if both supplied)
                  explode: true
                  in: query
                  name: asset_external_key
                  schema:
                    default: []
                    items:
                        pattern: ^[A-Za-z0-9-]+$
                        type: string
                    type: array
                  style: form
                - description: substring search (case-insensitive) on asset name, external_key, and active tag values
                  in: query
                  name: q
                  schema:
                    pattern: ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$
                    type: string
                - description: include rows for soft-deleted assets
                  in: query
                  name: include_deleted
                  schema:
                    default: false
                    type: boolean
                - description: comma-separated sort fields; prefix '-' for DESC
                  explode: false
                  in: query
                  name: sort
                  schema:
                    default: ""
                    pattern: ^(|(?:asset_last_seen|-asset_last_seen|asset_external_key|-asset_external_key|location_external_key|-location_external_key)(?:,(?:asset_last_seen|-asset_last_seen|asset_external_key|-asset_external_key|location_external_key|-location_external_key))*)$
                    type: string
                  style: form
            responses:
                "200":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/AssetLocationsResponse'
                    description: OK
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "400":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Bad Request
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "401":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unauthorized
                    headers:
                        WWW-Authenticate:
                            $ref: '#/components/headers/WWWAuthenticate'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "403":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Forbidden
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "404":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Not Found
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "405":
                    $ref: '#/components/responses/MethodNotAllowed'
                "429":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: rate_limited
                    headers:
                        Retry-After:
                            $ref: '#/components/headers/RetryAfter'
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                "500":
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Internal Server Error
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
                default:
                    content:
                        application/json:
                            schema:
                                $ref: '#/components/schemas/ErrorResponse'
                    description: Unmodeled error. Generated clients should treat any response not otherwise enumerated as a structured ErrorResponse.
                    headers:
                        X-RateLimit-Limit:
                            $ref: '#/components/headers/XRateLimitLimit'
                        X-RateLimit-Remaining:
                            $ref: '#/components/headers/XRateLimitRemaining'
                        X-RateLimit-Reset:
                            $ref: '#/components/headers/XRateLimitReset'
                        X-Request-Id:
                            $ref: '#/components/headers/XRequestId'
            security:
                - BearerAuth: []
            summary: List current asset locations
            tags:
                - reports
            x-required-scopes:
                - tracking:read
security:
    - BearerAuth: []
servers:
    - description: Preview (per-PR deploys). Preview-scoped API keys authenticate here only — they will fail with 401 against Production, and Production keys will fail here.
      url: https://app.preview.trakrf.id
    - description: Production. Production-scoped API keys authenticate here only — they will fail with 401 against Preview, and Preview keys will fail here.
      url: https://app.trakrf.id
tags:
    - description: Asset CRUD, history, and tag membership.
      name: assets
    - description: Location CRUD, hierarchy traversal, and tag membership.
      name: locations
    - description: Caller's organization context.
      name: orgs
    - description: Cross-cutting reporting endpoints (asset locations, etc.).
      name: reports
