# Ostuu API v1 — OpenAPI 3.1
# Generated by scripts/openapi/generate.mjs — do not hand-edit; update the generator.
# Source-of-truth: @ostuu/types + production serializers (Option B). See docs/openapi/README.md.
openapi: 3.1.0
info:
  title: Ostuu API
  version: 1.0.0-alpha.0
  summary: Ostuu API v1 (developer preview / alpha)
  description: >-
    Public HTTP API for Ostuu workspaces.


    **Stability:** API v1 is production-verified for the documented routes but remains an **alpha /
    developer-preview** surface. Field names are stable for early integrators; pin SDK versions
    explicitly.


    **Authentication:** `Authorization: Bearer <OSTUU_API_KEY>` using a workspace API key from
    Developer → API Keys.


    **Request IDs:** Responses include `x-request-id` and `error.requestId` / success correlation
    via the same header.


    **Idempotency:** Mutating writes require `Idempotency-Key` (72-hour retention). GET routes and
    `POST .../preflight` do not.


    **Publishing warning:** `POST /api/v1/posts/{id}/publish` may create real posts on connected
    destinations (Bluesky, LinkedIn, Facebook).


    Product UI says **Channels**; this API resource is **destinations**.


    Canonical TypeScript contracts: `@ostuu/types`. This OpenAPI document describes the same HTTP
    surface.
  contact:
    name: Ostuu
    url: https://www.ostuu.com
  license:
    name: Proprietary
    url: https://www.ostuu.com
servers:
  - url: https://www.ostuu.com
    description: Production
tags:
  - name: Brands
    description: Brand profiles in a workspace.
  - name: Destinations
    description: "Connected destinations (UI: Channels)."
  - name: Posts
    description: Post lifecycle, schedule, preflight, and publish.
  - name: Publish jobs
    description: Per-destination publish job status.
  - name: Media
    description: Media metadata and direct image upload.
  - name: Links
    description: Tracked / shortened links (rsi.gl).
paths:
  /api/v1/brands:
    get:
      operationId: listBrands
      tags:
        - Brands
      summary: List brands
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: cursor
          in: query
          schema:
            type: string
        - name: includeArchived
          in: query
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BrandListResponse"
        "400":
          description: Invalid cursor or query.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: INVALID_CURSOR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - brands:read
    post:
      operationId: createBrand
      tags:
        - Brands
      summary: Create brand
      security:
        - bearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          description: "Required on mutating writes. Reuse the same key only for an exact retry of the same
            request. Retention: 72 hours. Exact payload replay returns the original response; a
            different payload returns 409 IDEMPOTENCY_CONFLICT. Concurrent identical claims may
            return 409 IDEMPOTENCY_IN_PROGRESS. Validation failures release the claim so the same
            key can retry with a corrected body."
          schema:
            type: string
            minLength: 1
            maxLength: 256
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateBrandInput"
      responses:
        "201":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BrandResponse"
        "400":
          description: Validation error, invalid JSON, or IDEMPOTENCY_KEY_REQUIRED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: VALIDATION_ERROR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "409":
          description: Idempotency conflict / in progress, or domain conflict (e.g. LINK_SLUG_CONFLICT).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: IDEMPOTENCY_CONFLICT
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - brands:write
      x-ostuu-idempotency: true
  /api/v1/brands/{id}:
    get:
      operationId: getBrand
      tags:
        - Brands
      summary: Get brand
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Brand id
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BrandResponse"
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Brand not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - brands:read
    patch:
      operationId: updateBrand
      tags:
        - Brands
      summary: Update brand
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Brand id
        - name: Idempotency-Key
          in: header
          required: true
          description: "Required on mutating writes. Reuse the same key only for an exact retry of the same
            request. Retention: 72 hours. Exact payload replay returns the original response; a
            different payload returns 409 IDEMPOTENCY_CONFLICT. Concurrent identical claims may
            return 409 IDEMPOTENCY_IN_PROGRESS. Validation failures release the claim so the same
            key can retry with a corrected body."
          schema:
            type: string
            minLength: 1
            maxLength: 256
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateBrandInput"
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BrandResponse"
        "400":
          description: Validation error, invalid JSON, or IDEMPOTENCY_KEY_REQUIRED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: VALIDATION_ERROR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Brand not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
        "409":
          description: Idempotency conflict / in progress, or domain conflict (e.g. LINK_SLUG_CONFLICT).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: IDEMPOTENCY_CONFLICT
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - brands:write
      x-ostuu-idempotency: true
  /api/v1/brands/{id}/archive:
    post:
      operationId: archiveBrand
      tags:
        - Brands
      summary: Archive brand
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Brand id
        - name: Idempotency-Key
          in: header
          required: true
          description: "Required on mutating writes. Reuse the same key only for an exact retry of the same
            request. Retention: 72 hours. Exact payload replay returns the original response; a
            different payload returns 409 IDEMPOTENCY_CONFLICT. Concurrent identical claims may
            return 409 IDEMPOTENCY_IN_PROGRESS. Validation failures release the claim so the same
            key can retry with a corrected body."
          schema:
            type: string
            minLength: 1
            maxLength: 256
            example: 550e8400-e29b-41d4-a716-446655440000
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BrandResponse"
        "400":
          description: Validation error, invalid JSON, or IDEMPOTENCY_KEY_REQUIRED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: VALIDATION_ERROR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Brand not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
        "409":
          description: Idempotency conflict / in progress, or domain conflict (e.g. LINK_SLUG_CONFLICT).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: IDEMPOTENCY_CONFLICT
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - brands:write
      x-ostuu-idempotency: true
  /api/v1/brands/{id}/restore:
    post:
      operationId: restoreBrand
      tags:
        - Brands
      summary: Restore brand
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Brand id
        - name: Idempotency-Key
          in: header
          required: true
          description: "Required on mutating writes. Reuse the same key only for an exact retry of the same
            request. Retention: 72 hours. Exact payload replay returns the original response; a
            different payload returns 409 IDEMPOTENCY_CONFLICT. Concurrent identical claims may
            return 409 IDEMPOTENCY_IN_PROGRESS. Validation failures release the claim so the same
            key can retry with a corrected body."
          schema:
            type: string
            minLength: 1
            maxLength: 256
            example: 550e8400-e29b-41d4-a716-446655440000
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BrandResponse"
        "400":
          description: Validation error, invalid JSON, or IDEMPOTENCY_KEY_REQUIRED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: VALIDATION_ERROR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Brand not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
        "409":
          description: Idempotency conflict / in progress, or domain conflict (e.g. LINK_SLUG_CONFLICT).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: IDEMPOTENCY_CONFLICT
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - brands:write
      x-ostuu-idempotency: true
  /api/v1/destinations:
    get:
      operationId: listDestinations
      tags:
        - Destinations
      summary: List destinations
      description: Lists connected destinations. Product UI refers to these as Channels.
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: cursor
          in: query
          schema:
            type: string
        - name: includeInactive
          in: query
          schema:
            type: boolean
            default: false
        - name: brandId
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DestinationListResponse"
        "400":
          description: Invalid cursor or query.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: INVALID_CURSOR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - destinations:read
  /api/v1/destinations/{id}:
    get:
      operationId: getDestination
      tags:
        - Destinations
      summary: Get destination
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Destination id (Channel)
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DestinationResponse"
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Destination not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - destinations:read
  /api/v1/destinations/{id}/brand:
    put:
      operationId: setDestinationBrand
      tags:
        - Destinations
      summary: Attach or remove brand on a destination
      description: 'Body `{ "brandId": "<id>" }` attaches; `{ "brandId": null }` removes the brand link
        without disconnecting credentials. Attach to an archived brand returns BRAND_ARCHIVED.'
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Destination id (Channel)
        - name: Idempotency-Key
          in: header
          required: true
          description: "Required on mutating writes. Reuse the same key only for an exact retry of the same
            request. Retention: 72 hours. Exact payload replay returns the original response; a
            different payload returns 409 IDEMPOTENCY_CONFLICT. Concurrent identical claims may
            return 409 IDEMPOTENCY_IN_PROGRESS. Validation failures release the claim so the same
            key can retry with a corrected body."
          schema:
            type: string
            minLength: 1
            maxLength: 256
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - brandId
              properties:
                brandId:
                  type:
                    - string
                    - "null"
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DestinationBrandUpdateResponse"
        "400":
          description: Validation error, invalid JSON, or IDEMPOTENCY_KEY_REQUIRED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: VALIDATION_ERROR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Destination or brand not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
        "409":
          description: Idempotency conflict / in progress, or domain conflict (e.g. LINK_SLUG_CONFLICT).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: IDEMPOTENCY_CONFLICT
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - destinations:write
      x-ostuu-idempotency: true
  /api/v1/posts:
    get:
      operationId: listPosts
      tags:
        - Posts
      summary: List posts
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: cursor
          in: query
          schema:
            type: string
        - name: includeArchived
          in: query
          schema:
            type: boolean
            default: false
        - name: brandId
          in: query
          schema:
            type: string
        - name: status
          in: query
          schema:
            type: string
        - name: scheduledAfter
          in: query
          schema:
            type: string
            format: date-time
        - name: scheduledBefore
          in: query
          schema:
            type: string
            format: date-time
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PostListResponse"
        "400":
          description: Invalid cursor or query.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: INVALID_CURSOR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - posts:read
    post:
      operationId: createPost
      tags:
        - Posts
      summary: Create post
      security:
        - bearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          description: "Required on mutating writes. Reuse the same key only for an exact retry of the same
            request. Retention: 72 hours. Exact payload replay returns the original response; a
            different payload returns 409 IDEMPOTENCY_CONFLICT. Concurrent identical claims may
            return 409 IDEMPOTENCY_IN_PROGRESS. Validation failures release the claim so the same
            key can retry with a corrected body."
          schema:
            type: string
            minLength: 1
            maxLength: 256
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreatePostInput"
      responses:
        "201":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PostResponse"
        "400":
          description: Validation error, invalid JSON, or IDEMPOTENCY_KEY_REQUIRED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: VALIDATION_ERROR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "409":
          description: Idempotency conflict / in progress, or domain conflict (e.g. LINK_SLUG_CONFLICT).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: IDEMPOTENCY_CONFLICT
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - posts:write
      x-ostuu-idempotency: true
  /api/v1/posts/{id}:
    get:
      operationId: getPost
      tags:
        - Posts
      summary: Get post
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Post id
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PostResponse"
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Post not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - posts:read
    patch:
      operationId: updatePost
      tags:
        - Posts
      summary: Update post
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Post id
        - name: Idempotency-Key
          in: header
          required: true
          description: "Required on mutating writes. Reuse the same key only for an exact retry of the same
            request. Retention: 72 hours. Exact payload replay returns the original response; a
            different payload returns 409 IDEMPOTENCY_CONFLICT. Concurrent identical claims may
            return 409 IDEMPOTENCY_IN_PROGRESS. Validation failures release the claim so the same
            key can retry with a corrected body."
          schema:
            type: string
            minLength: 1
            maxLength: 256
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdatePostInput"
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PostResponse"
        "400":
          description: Validation error, invalid JSON, or IDEMPOTENCY_KEY_REQUIRED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: VALIDATION_ERROR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Post not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
        "409":
          description: Idempotency conflict / in progress, or domain conflict (e.g. LINK_SLUG_CONFLICT).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: IDEMPOTENCY_CONFLICT
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - posts:write
      x-ostuu-idempotency: true
  /api/v1/posts/{id}/schedule:
    post:
      operationId: schedulePost
      tags:
        - Posts
      summary: Schedule post
      description: Rejects timestamps already in the past beyond a short grace window (~60s) with
        INVALID_SCHEDULE_TIME. Empty destinations → DESTINATION_REQUIRED.
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Post id
        - name: Idempotency-Key
          in: header
          required: true
          description: "Required on mutating writes. Reuse the same key only for an exact retry of the same
            request. Retention: 72 hours. Exact payload replay returns the original response; a
            different payload returns 409 IDEMPOTENCY_CONFLICT. Concurrent identical claims may
            return 409 IDEMPOTENCY_IN_PROGRESS. Validation failures release the claim so the same
            key can retry with a corrected body."
          schema:
            type: string
            minLength: 1
            maxLength: 256
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SchedulePostInput"
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PostResponse"
        "400":
          description: Validation error, invalid JSON, or IDEMPOTENCY_KEY_REQUIRED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: VALIDATION_ERROR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Post not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
        "409":
          description: Idempotency conflict / in progress, or domain conflict (e.g. LINK_SLUG_CONFLICT).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: IDEMPOTENCY_CONFLICT
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - posts:write
      x-ostuu-idempotency: true
  /api/v1/posts/{id}/unschedule:
    post:
      operationId: unschedulePost
      tags:
        - Posts
      summary: Unschedule post
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Post id
        - name: Idempotency-Key
          in: header
          required: true
          description: "Required on mutating writes. Reuse the same key only for an exact retry of the same
            request. Retention: 72 hours. Exact payload replay returns the original response; a
            different payload returns 409 IDEMPOTENCY_CONFLICT. Concurrent identical claims may
            return 409 IDEMPOTENCY_IN_PROGRESS. Validation failures release the claim so the same
            key can retry with a corrected body."
          schema:
            type: string
            minLength: 1
            maxLength: 256
            example: 550e8400-e29b-41d4-a716-446655440000
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PostResponse"
        "400":
          description: Validation error, invalid JSON, or IDEMPOTENCY_KEY_REQUIRED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: VALIDATION_ERROR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Post not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
        "409":
          description: Idempotency conflict / in progress, or domain conflict (e.g. LINK_SLUG_CONFLICT).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: IDEMPOTENCY_CONFLICT
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - posts:write
      x-ostuu-idempotency: true
  /api/v1/posts/{id}/archive:
    post:
      operationId: archivePost
      tags:
        - Posts
      summary: Archive post
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Post id
        - name: Idempotency-Key
          in: header
          required: true
          description: "Required on mutating writes. Reuse the same key only for an exact retry of the same
            request. Retention: 72 hours. Exact payload replay returns the original response; a
            different payload returns 409 IDEMPOTENCY_CONFLICT. Concurrent identical claims may
            return 409 IDEMPOTENCY_IN_PROGRESS. Validation failures release the claim so the same
            key can retry with a corrected body."
          schema:
            type: string
            minLength: 1
            maxLength: 256
            example: 550e8400-e29b-41d4-a716-446655440000
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PostResponse"
        "400":
          description: Validation error, invalid JSON, or IDEMPOTENCY_KEY_REQUIRED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: VALIDATION_ERROR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Post not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
        "409":
          description: Idempotency conflict / in progress, or domain conflict (e.g. LINK_SLUG_CONFLICT).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: IDEMPOTENCY_CONFLICT
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - posts:write
      x-ostuu-idempotency: true
  /api/v1/posts/{id}/restore:
    post:
      operationId: restorePost
      tags:
        - Posts
      summary: Restore post
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Post id
        - name: Idempotency-Key
          in: header
          required: true
          description: "Required on mutating writes. Reuse the same key only for an exact retry of the same
            request. Retention: 72 hours. Exact payload replay returns the original response; a
            different payload returns 409 IDEMPOTENCY_CONFLICT. Concurrent identical claims may
            return 409 IDEMPOTENCY_IN_PROGRESS. Validation failures release the claim so the same
            key can retry with a corrected body."
          schema:
            type: string
            minLength: 1
            maxLength: 256
            example: 550e8400-e29b-41d4-a716-446655440000
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PostResponse"
        "400":
          description: Validation error, invalid JSON, or IDEMPOTENCY_KEY_REQUIRED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: VALIDATION_ERROR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Post not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
        "409":
          description: Idempotency conflict / in progress, or domain conflict (e.g. LINK_SLUG_CONFLICT).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: IDEMPOTENCY_CONFLICT
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - posts:write
      x-ostuu-idempotency: true
  /api/v1/posts/{id}/preflight:
    post:
      operationId: preflightPost
      tags:
        - Posts
      summary: Preflight post
      description: Side-effect free validation. Does **not** create short links, publish jobs, or usage
        reservations. Does **not** require Idempotency-Key.
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Post id
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                destinationIds:
                  type: array
                  items:
                    type: string
                  maxItems: 20
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PreflightResponse"
        "400":
          description: Validation error.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: VALIDATION_ERROR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Post not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - posts:publish
      x-ostuu-idempotency: false
  /api/v1/posts/{id}/publish:
    post:
      operationId: publishPost
      tags:
        - Posts
      summary: Publish post now
      description: >-
        **Warning:** May create real external posts on connected destinations.


        Synchronous through the current orchestrator. Response `data.status` is `completed` or
        `partial`.

        Per-destination outcomes: `published`, `already_published`, `failed`.

        Scheduled posts must be unscheduled before immediate publish (`POST_ALREADY_SCHEDULED`).

        In-flight jobs return `POST_PROCESSING`. Already-published destinations are not republished.
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Post id
        - name: Idempotency-Key
          in: header
          required: true
          description: "Required on mutating writes. Reuse the same key only for an exact retry of the same
            request. Retention: 72 hours. Exact payload replay returns the original response; a
            different payload returns 409 IDEMPOTENCY_CONFLICT. Concurrent identical claims may
            return 409 IDEMPOTENCY_IN_PROGRESS. Validation failures release the claim so the same
            key can retry with a corrected body."
          schema:
            type: string
            minLength: 1
            maxLength: 256
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                destinationIds:
                  type: array
                  items:
                    type: string
                  maxItems: 20
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublishResponse"
        "400":
          description: Validation error, invalid JSON, or IDEMPOTENCY_KEY_REQUIRED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: VALIDATION_ERROR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Post not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
        "409":
          description: Idempotency conflict / in progress, or domain conflict (e.g. LINK_SLUG_CONFLICT).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: IDEMPOTENCY_CONFLICT
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - posts:publish
      x-ostuu-idempotency: true
  /api/v1/posts/{id}/jobs:
    get:
      operationId: listPublishJobs
      tags:
        - Publish jobs
      summary: List publish jobs for a post
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Post id
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublishJobListResponse"
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Post not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - jobs:read
  /api/v1/posts/{id}/jobs/{jobId}:
    get:
      operationId: getPublishJob
      tags:
        - Publish jobs
      summary: Get publish job
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Post id
        - name: jobId
          in: path
          required: true
          schema:
            type: string
          description: Publish job id
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublishJobResponse"
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Job not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: JOB_NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - jobs:read
  /api/v1/media:
    get:
      operationId: listMedia
      tags:
        - Media
      summary: List media
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: cursor
          in: query
          schema:
            type: string
        - name: brandId
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaListResponse"
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - media:read
    post:
      operationId: uploadMediaImage
      tags:
        - Media
      summary: Upload image (multipart)
      description: |-
        Direct multipart upload for **images only**.

        - Max file size: **3.5 MB** (3,670,016 bytes)
        - Max request Content-Length: **4.25 MB**
        - Video → `MEDIA_VIDEO_NOT_SUPPORTED_VIA_API`
        - SVG/HTML blocked
        - Response is metadata only (no private Blob URLs)
        - File field name: `file` or `media` (exactly one)
      security:
        - bearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          description: "Required on mutating writes. Reuse the same key only for an exact retry of the same
            request. Retention: 72 hours. Exact payload replay returns the original response; a
            different payload returns 409 IDEMPOTENCY_CONFLICT. Concurrent identical claims may
            return 409 IDEMPOTENCY_IN_PROGRESS. Validation failures release the claim so the same
            key can retry with a corrected body."
          schema:
            type: string
            minLength: 1
            maxLength: 256
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: Image file (preferred field name).
                media:
                  type: string
                  format: binary
                  description: Alias for file.
                brandId:
                  type: string
                  description: Optional brand id.
                altText:
                  type: string
                  description: Optional alt text.
      responses:
        "201":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaResponse"
        "400":
          description: Validation error, invalid JSON, or IDEMPOTENCY_KEY_REQUIRED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: VALIDATION_ERROR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "409":
          description: Idempotency conflict / in progress, or domain conflict (e.g. LINK_SLUG_CONFLICT).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: IDEMPOTENCY_CONFLICT
                  message: Example error message
                  requestId: req_example_01HXYZ
        "413":
          description: File or request too large.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: MEDIA_TOO_LARGE_FOR_DIRECT_UPLOAD
                  message: Example error message
                  requestId: req_example_01HXYZ
        "415":
          description: Unsupported media type.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNSUPPORTED_MEDIA_TYPE
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - media:write
      x-ostuu-idempotency: true
  /api/v1/media/{id}:
    get:
      operationId: getMedia
      tags:
        - Media
      summary: Get media metadata
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Media asset id
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaResponse"
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Media not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: MEDIA_NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - media:read
    delete:
      operationId: deleteMedia
      tags:
        - Media
      summary: Delete media
      description: Fails with MEDIA_IN_USE when referenced by posts.
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Media asset id
        - name: Idempotency-Key
          in: header
          required: true
          description: "Required on mutating writes. Reuse the same key only for an exact retry of the same
            request. Retention: 72 hours. Exact payload replay returns the original response; a
            different payload returns 409 IDEMPOTENCY_CONFLICT. Concurrent identical claims may
            return 409 IDEMPOTENCY_IN_PROGRESS. Validation failures release the claim so the same
            key can retry with a corrected body."
          schema:
            type: string
            minLength: 1
            maxLength: 256
            example: 550e8400-e29b-41d4-a716-446655440000
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MediaDeleteResponse"
        "400":
          description: Validation error, invalid JSON, or IDEMPOTENCY_KEY_REQUIRED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: VALIDATION_ERROR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Media not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: MEDIA_NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
        "409":
          description: Idempotency conflict / in progress, or domain conflict (e.g. LINK_SLUG_CONFLICT).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: IDEMPOTENCY_CONFLICT
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - media:write
      x-ostuu-idempotency: true
  /api/v1/links:
    get:
      operationId: listLinks
      tags:
        - Links
      summary: List links
      security:
        - bearerAuth: []
      parameters:
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
        - name: cursor
          in: query
          schema:
            type: string
        - name: brandId
          in: query
          schema:
            type: string
        - name: postId
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LinkListResponse"
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - links:read
    post:
      operationId: createLink
      tags:
        - Links
      summary: Create short / tracked link
      description: >-
        Modes: PRESERVE, TRACK, SHORTEN (default provider rsi.gl / RSI_GL).

        UTM is applied before shortening when requested.

        Custom slugs are globally unique for rsi.gl; conflicts → LINK_SLUG_CONFLICT.

        Destination-specific UTM with a custom slug across multiple destinations →
        LINK_SLUG_MULTI_DESTINATION.

        Direct links may have `postId = null`. Authenticated clients may see full URLs in responses;
        logs/CauseWise do not receive full URLs.
      security:
        - bearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: true
          description: "Required on mutating writes. Reuse the same key only for an exact retry of the same
            request. Retention: 72 hours. Exact payload replay returns the original response; a
            different payload returns 409 IDEMPOTENCY_CONFLICT. Concurrent identical claims may
            return 409 IDEMPOTENCY_IN_PROGRESS. Validation failures release the claim so the same
            key can retry with a corrected body."
          schema:
            type: string
            minLength: 1
            maxLength: 256
            example: 550e8400-e29b-41d4-a716-446655440000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateLinkInput"
      responses:
        "201":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LinkResponse"
        "400":
          description: Validation error, invalid JSON, or IDEMPOTENCY_KEY_REQUIRED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: VALIDATION_ERROR
                  message: Example error message
                  requestId: req_example_01HXYZ
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "409":
          description: Idempotency conflict / in progress, or domain conflict (e.g. LINK_SLUG_CONFLICT).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: IDEMPOTENCY_CONFLICT
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - links:write
      x-ostuu-idempotency: true
  /api/v1/links/{id}:
    get:
      operationId: getLink
      tags:
        - Links
      summary: Get link
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
          description: Link id
      responses:
        "200":
          description: Success
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LinkResponse"
        "401":
          description: Missing or invalid API key (non-enumerating).
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: UNAUTHORIZED
                  message: Example error message
                  requestId: req_example_01HXYZ
        "403":
          description: Insufficient API key scope, missing access.api entitlement, or WORKSPACE_SUSPENDED.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: FORBIDDEN
                  message: Example error message
                  requestId: req_example_01HXYZ
        "404":
          description: Link not found.
          headers:
            x-request-id:
              description: Request correlation ID (echoed from the request or generated).
              schema:
                type: string
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorEnvelope"
              example:
                error:
                  code: LINK_NOT_FOUND
                  message: Example error message
                  requestId: req_example_01HXYZ
      x-ostuu-scopes:
        - links:read
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Ostuu API key
      description: "Workspace API key from Developer → API Keys. Send as `Authorization: Bearer <key>`.
        Missing/invalid keys return the same 401 UNAUTHORIZED message (non-enumerating)."
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: "Required on mutating writes. Reuse the same key only for an exact retry of the same
        request. Retention: 72 hours. Exact payload replay returns the original response; a
        different payload returns 409 IDEMPOTENCY_CONFLICT. Concurrent identical claims may return
        409 IDEMPOTENCY_IN_PROGRESS. Validation failures release the claim so the same key can retry
        with a corrected body."
      schema:
        type: string
        minLength: 1
        maxLength: 256
        example: 550e8400-e29b-41d4-a716-446655440000
  schemas:
    ApiErrorEnvelope:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
            - requestId
          properties:
            code:
              type: string
              examples:
                - UNAUTHORIZED
                - FORBIDDEN
                - VALIDATION_ERROR
            message:
              type: string
            requestId:
              type: string
    ApiErrorCode:
      type: string
      description: Stable machine-readable error codes used by API v1.
      enum:
        - UNAUTHORIZED
        - FORBIDDEN
        - ENTITLEMENT_REQUIRED
        - WORKSPACE_SUSPENDED
        - NOT_FOUND
        - VALIDATION_ERROR
        - INVALID_JSON
        - INVALID_CURSOR
        - INVALID_BRAND
        - PAYLOAD_TOO_LARGE
        - UNSUPPORTED_MEDIA_TYPE
        - INVALID_MULTIPART
        - IDEMPOTENCY_KEY_REQUIRED
        - IDEMPOTENCY_CONFLICT
        - IDEMPOTENCY_IN_PROGRESS
        - INVALID_SCHEDULE_TIME
        - DESTINATION_REQUIRED
        - POST_PROCESSING
        - POST_ALREADY_SCHEDULED
        - POST_NOT_FOUND
        - POST_ARCHIVED
        - POST_NOT_SCHEDULED
        - POST_NOT_EDITABLE
        - BRAND_ARCHIVED
        - MEDIA_TOO_LARGE_FOR_DIRECT_UPLOAD
        - MEDIA_VIDEO_NOT_SUPPORTED_VIA_API
        - MEDIA_CORRUPT
        - MEDIA_IN_USE
        - MEDIA_NOT_FOUND
        - MEDIA_TYPE_UNSUPPORTED
        - MEDIA_UPLOAD_FAILED
        - LINK_SLUG_CONFLICT
        - LINK_SLUG_MULTI_DESTINATION
        - LINK_NOT_FOUND
        - JOB_NOT_FOUND
        - INTERNAL
    ApiKeyScope:
      type: string
      enum:
        - brands:read
        - brands:write
        - destinations:read
        - destinations:write
        - media:read
        - media:write
        - posts:read
        - posts:write
        - posts:publish
        - jobs:read
        - links:read
        - links:write
    CursorPagination:
      type: object
      required:
        - limit
        - nextCursor
      properties:
        limit:
          type: integer
        nextCursor:
          type:
            - string
            - "null"
    PostStatus:
      type: string
      enum:
        - DRAFT
        - SCHEDULED
        - PUBLISHED
        - FAILED
        - ARCHIVED
    MediaType:
      type: string
      enum:
        - IMAGE
        - VIDEO
    MediaStatus:
      type: string
      enum:
        - UPLOADING
        - READY
        - INVALID
        - DELETED
    LinkMode:
      type: string
      enum:
        - PRESERVE
        - TRACK
        - SHORTEN
      description: Link processing mode (LinkPolicy).
    LinkPolicy:
      type: object
      description: Link processing policy applied at create/publish time.
      properties:
        mode:
          $ref: "#/components/schemas/LinkMode"
        addUtmParameters:
          type: boolean
        utmSource:
          type:
            - string
            - "null"
        utmMedium:
          type:
            - string
            - "null"
        utmCampaign:
          type:
            - string
            - "null"
        customSlug:
          type:
            - string
            - "null"
    PublicBrand:
      type: object
      required:
        - id
        - slug
        - brandName
        - brandColor
        - logoUrl
        - website
        - description
        - archivedAt
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
        slug:
          type: string
        brandName:
          type:
            - string
            - "null"
        brandColor:
          type: string
        logoUrl:
          type:
            - string
            - "null"
        website:
          type:
            - string
            - "null"
        description:
          type:
            - string
            - "null"
        archivedAt:
          type:
            - string
            - "null"
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    BrandListResponse:
      type: object
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PublicBrand"
        pagination:
          allOf:
            - $ref: "#/components/schemas/CursorPagination"
            - type: object
              properties:
                includeArchived:
                  type: boolean
    BrandResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: "#/components/schemas/PublicBrand"
    CreateBrandInput:
      type: object
      required:
        - brandName
      properties:
        brandName:
          type: string
        slug:
          type:
            - string
            - "null"
        description:
          type:
            - string
            - "null"
        website:
          type:
            - string
            - "null"
        brandColor:
          type:
            - string
            - "null"
    UpdateBrandInput:
      type: object
      properties:
        brandName:
          type: string
        description:
          type:
            - string
            - "null"
        website:
          type:
            - string
            - "null"
        brandColor:
          type:
            - string
            - "null"
        logoUrl:
          type:
            - string
            - "null"
    PublicDestination:
      type: object
      required:
        - id
        - provider
        - label
        - handle
        - externalId
        - destinationType
        - isActive
        - brandId
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
        provider:
          type: string
        label:
          type:
            - string
            - "null"
        handle:
          type:
            - string
            - "null"
        externalId:
          type:
            - string
            - "null"
        destinationType:
          type:
            - string
            - "null"
        isActive:
          type: boolean
        brandId:
          type:
            - string
            - "null"
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    DestinationListResponse:
      type: object
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PublicDestination"
        pagination:
          allOf:
            - $ref: "#/components/schemas/CursorPagination"
            - type: object
              properties:
                includeInactive:
                  type: boolean
                brandId:
                  type:
                    - string
                    - "null"
    DestinationResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: "#/components/schemas/PublicDestination"
    DestinationBrandUpdateResponse:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - id
            - brandId
          properties:
            id:
              type: string
            brandId:
              type:
                - string
                - "null"
    PublicPost:
      type: object
      required:
        - id
        - brandId
        - campaignId
        - status
        - content
        - primaryUrl
        - mediaAssetIds
        - destinationIds
        - scheduledAt
        - publishedAt
        - createdAt
        - updatedAt
        - archivedAt
      properties:
        id:
          type: string
        brandId:
          type:
            - string
            - "null"
        campaignId:
          type:
            - string
            - "null"
        status:
          $ref: "#/components/schemas/PostStatus"
        content:
          type: object
          required:
            - text
            - title
          properties:
            text:
              type: string
            title:
              type:
                - string
                - "null"
        primaryUrl:
          type:
            - string
            - "null"
        mediaAssetIds:
          type: array
          items:
            type: string
        destinationIds:
          type: array
          items:
            type: string
        scheduledAt:
          type:
            - string
            - "null"
          format: date-time
        publishedAt:
          type:
            - string
            - "null"
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        archivedAt:
          type:
            - string
            - "null"
          description: Always null until a durable archive timestamp exists; use status ARCHIVED.
    PostListResponse:
      type: object
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PublicPost"
        pagination:
          allOf:
            - $ref: "#/components/schemas/CursorPagination"
            - type: object
              properties:
                includeArchived:
                  type: boolean
                brandId:
                  type:
                    - string
                    - "null"
                status:
                  type:
                    - string
                    - "null"
    PostResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: "#/components/schemas/PublicPost"
    CreatePostInput:
      type: object
      required:
        - content
      properties:
        brandId:
          type:
            - string
            - "null"
        campaignId:
          type:
            - string
            - "null"
        content:
          type: object
          required:
            - text
          properties:
            text:
              type: string
            title:
              type:
                - string
                - "null"
        primaryUrl:
          type:
            - string
            - "null"
        mediaAssetIds:
          type: array
          items:
            type: string
        destinationIds:
          type: array
          items:
            type: string
          maxItems: 20
        autoOptimizeMedia:
          type: boolean
        scheduledAt:
          type:
            - string
            - "null"
          format: date-time
    UpdatePostInput:
      type: object
      properties:
        brandId:
          type:
            - string
            - "null"
        campaignId:
          type:
            - string
            - "null"
        content:
          type: object
          properties:
            text:
              type: string
            title:
              type:
                - string
                - "null"
        primaryUrl:
          type:
            - string
            - "null"
        mediaAssetIds:
          type: array
          items:
            type: string
        destinationIds:
          type: array
          items:
            type: string
          maxItems: 20
        autoOptimizeMedia:
          type: boolean
    SchedulePostInput:
      type: object
      required:
        - scheduledAt
      properties:
        scheduledAt:
          type: string
          format: date-time
        destinationIds:
          type: array
          items:
            type: string
          maxItems: 20
    PublicPublishJob:
      type: object
      required:
        - id
        - postId
        - destinationId
        - provider
        - status
        - externalId
        - externalUrl
        - publishedAt
        - failedAt
        - errorCode
        - errorMessage
        - attemptCount
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
        postId:
          type: string
        destinationId:
          type: string
        provider:
          type: string
        status:
          type: string
          description: Publish job status (PostChannel status).
        externalId:
          type:
            - string
            - "null"
        externalUrl:
          type:
            - string
            - "null"
        publishedAt:
          type:
            - string
            - "null"
          format: date-time
        failedAt:
          type:
            - string
            - "null"
          format: date-time
        errorCode:
          type:
            - string
            - "null"
        errorMessage:
          type:
            - string
            - "null"
        attemptCount:
          type: integer
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    PublishJobListResponse:
      type: object
      required:
        - data
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PublicPublishJob"
    PublishJobResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: "#/components/schemas/PublicPublishJob"
    PreflightIssue:
      type: object
      required:
        - code
        - message
        - severity
      properties:
        code:
          type: string
        message:
          type: string
        severity:
          type: string
          enum:
            - error
            - warning
        destinationId:
          type: string
    PreflightDestinationResult:
      type: object
      required:
        - destinationId
        - provider
        - ok
        - errors
        - warnings
      properties:
        destinationId:
          type: string
        provider:
          type: string
        ok:
          type: boolean
        errors:
          type: array
          items:
            $ref: "#/components/schemas/PreflightIssue"
        warnings:
          type: array
          items:
            $ref: "#/components/schemas/PreflightIssue"
    PreflightResult:
      type: object
      required:
        - ok
        - destinations
        - errors
        - warnings
      properties:
        ok:
          type: boolean
        destinations:
          type: array
          items:
            $ref: "#/components/schemas/PreflightDestinationResult"
        errors:
          type: array
          items:
            $ref: "#/components/schemas/PreflightIssue"
        warnings:
          type: array
          items:
            $ref: "#/components/schemas/PreflightIssue"
    PreflightResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: "#/components/schemas/PreflightResult"
    PublishDestinationResult:
      type: object
      required:
        - destinationId
        - outcome
      properties:
        destinationId:
          type: string
        outcome:
          type: string
          enum:
            - published
            - already_published
            - failed
        jobId:
          type: string
        externalUrl:
          type:
            - string
            - "null"
        error:
          type: string
        errorCode:
          type: string
    PublishResult:
      type: object
      required:
        - status
        - post
        - results
        - jobs
        - counts
      properties:
        status:
          type: string
          enum:
            - completed
            - partial
        post:
          oneOf:
            - $ref: "#/components/schemas/PublicPost"
            - type: "null"
        results:
          type: array
          items:
            $ref: "#/components/schemas/PublishDestinationResult"
        jobs:
          type: array
          items:
            $ref: "#/components/schemas/PublicPublishJob"
        counts:
          type: object
          required:
            - published
            - alreadyPublished
            - failed
          properties:
            published:
              type: integer
            alreadyPublished:
              type: integer
            failed:
              type: integer
    PublishResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: "#/components/schemas/PublishResult"
    PublicMedia:
      type: object
      required:
        - id
        - brandId
        - type
        - mimeType
        - sizeBytes
        - width
        - height
        - durationSeconds
        - filename
        - altText
        - status
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
        brandId:
          type:
            - string
            - "null"
        type:
          $ref: "#/components/schemas/MediaType"
        mimeType:
          type:
            - string
            - "null"
        sizeBytes:
          type:
            - integer
            - "null"
        width:
          type:
            - integer
            - "null"
        height:
          type:
            - integer
            - "null"
        durationSeconds:
          type:
            - number
            - "null"
        filename:
          type:
            - string
            - "null"
        altText:
          type:
            - string
            - "null"
        status:
          $ref: "#/components/schemas/MediaStatus"
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    MediaListResponse:
      type: object
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PublicMedia"
        pagination:
          allOf:
            - $ref: "#/components/schemas/CursorPagination"
            - type: object
              properties:
                brandId:
                  type:
                    - string
                    - "null"
    MediaResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: "#/components/schemas/PublicMedia"
    MediaDeleteResponse:
      type: object
      required:
        - data
      properties:
        data:
          type: object
          required:
            - id
            - deleted
            - alreadyDeleted
            - storageReclaimed
          properties:
            id:
              type: string
            deleted:
              type: boolean
              const: true
            alreadyDeleted:
              type: boolean
            storageReclaimed:
              type: boolean
    PublicLink:
      type: object
      required:
        - id
        - brandId
        - destinationId
        - postId
        - originalUrl
        - trackedUrl
        - shortUrl
        - provider
        - slug
        - externalId
        - providerClickCount
        - clickCount
        - campaignId
        - createdAt
        - updatedAt
      properties:
        id:
          type: string
        brandId:
          type:
            - string
            - "null"
        destinationId:
          type:
            - string
            - "null"
        postId:
          type:
            - string
            - "null"
        originalUrl:
          type: string
        trackedUrl:
          type: string
        shortUrl:
          type:
            - string
            - "null"
        provider:
          type:
            - string
            - "null"
          description: e.g. RSI_GL
        slug:
          type:
            - string
            - "null"
        externalId:
          type:
            - string
            - "null"
        providerClickCount:
          type: integer
        clickCount:
          type: integer
        campaignId:
          type:
            - string
            - "null"
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
    LinkListResponse:
      type: object
      required:
        - data
        - pagination
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/PublicLink"
        pagination:
          allOf:
            - $ref: "#/components/schemas/CursorPagination"
            - type: object
              properties:
                brandId:
                  type:
                    - string
                    - "null"
                postId:
                  type:
                    - string
                    - "null"
    LinkResponse:
      type: object
      required:
        - data
      properties:
        data:
          $ref: "#/components/schemas/PublicLink"
    CreateLinkInput:
      type: object
      properties:
        url:
          type: string
          description: Destination URL (preferred).
        destinationUrl:
          type: string
          description: Alias for url.
        postId:
          type:
            - string
            - "null"
        brandId:
          type:
            - string
            - "null"
        destinationId:
          type:
            - string
            - "null"
        campaignId:
          type:
            - string
            - "null"
        title:
          type:
            - string
            - "null"
        customSlug:
          type:
            - string
            - "null"
        slug:
          type:
            - string
            - "null"
        mode:
          $ref: "#/components/schemas/LinkMode"
        addUtmParameters:
          type: boolean
        utmSource:
          type:
            - string
            - "null"
        utmMedium:
          type:
            - string
            - "null"
        utmCampaign:
          type:
            - string
            - "null"
        utm:
          type: object
          properties:
            source:
              type:
                - string
                - "null"
            medium:
              type:
                - string
                - "null"
            campaign:
              type:
                - string
                - "null"
