openapi: 3.1.0
info:
  title: Get Posting API
  version: 1.0.0
  description: Create, validate, and publish compositions through an existing Get Posting workspace.
externalDocs:
  description: Get Posting developer resources
  url: https://getposting.com/developers
servers:
  - url: https://getposting.com/api/v1
security:
  - bearerAuth: []
paths:
  /accounts:
    get:
      operationId: listAccounts
      parameters:
        - { $ref: "#/components/parameters/Limit" }
        - { $ref: "#/components/parameters/Cursor" }
      responses:
        "200": { description: Visible social accounts, content: { application/json: { schema: { $ref: "#/components/schemas/AccountCollection" } } } }
        default: { $ref: "#/components/responses/Error" }
  /media:
    get:
      operationId: listMedia
      parameters:
        - { $ref: "#/components/parameters/Limit" }
        - { $ref: "#/components/parameters/Cursor" }
        - { name: query, in: query, schema: { type: string } }
      responses:
        "200": { description: Media collection, content: { application/json: { schema: { $ref: "#/components/schemas/MediaCollection" } } } }
        default: { $ref: "#/components/responses/Error" }
    post:
      operationId: uploadMedia
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              additionalProperties: false
              required: [file]
              properties:
                file: { type: string, format: binary }
                name: { type: string, maxLength: 200 }
      responses:
        "201": { description: Ready media, content: { application/json: { schema: { $ref: "#/components/schemas/Media" } } } }
        default: { $ref: "#/components/responses/Error" }
  /compositions:
    get:
      operationId: listCompositions
      parameters:
        - { $ref: "#/components/parameters/Limit" }
        - { $ref: "#/components/parameters/Cursor" }
        - { name: status, in: query, schema: { $ref: "#/components/schemas/CompositionStatus" } }
        - { name: q, in: query, description: Text search over root content, schema: { type: string } }
        - { name: scheduledAfter, in: query, description: Lower bound (inclusive) on scheduled time; implies status scheduled, soonest first, schema: { type: string, format: date-time } }
        - { name: scheduledBefore, in: query, description: Upper bound (inclusive) on scheduled time; implies status scheduled, soonest first, schema: { type: string, format: date-time } }
      responses:
        "200": { description: Composition summaries, content: { application/json: { schema: { $ref: "#/components/schemas/CompositionCollection" } } } }
        default: { $ref: "#/components/responses/Error" }
    post:
      operationId: createComposition
      description: Creates a new draft. Every accepted request creates a new composition.
      requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/CompositionWrite" } } } }
      responses:
        "201": { description: Draft created, content: { application/json: { schema: { $ref: "#/components/schemas/Composition" } } } }
        default: { $ref: "#/components/responses/Error" }
  /compositions/{compositionId}:
    parameters: [{ name: compositionId, in: path, required: true, schema: { type: string, pattern: '^comp_' } }]
    get:
      operationId: getComposition
      responses:
        "200": { description: Composition detail, content: { application/json: { schema: { $ref: "#/components/schemas/Composition" } } } }
        default: { $ref: "#/components/responses/Error" }
    patch:
      operationId: updateComposition
      description: Updates only supplied root fields. If targets is supplied, it atomically replaces the complete target array; omitted target content and settings inherit from the root and defaults.
      requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/CompositionPatch" } } } }
      responses:
        "200": { description: Updated draft, content: { application/json: { schema: { $ref: "#/components/schemas/Composition" } } } }
        default: { $ref: "#/components/responses/Error" }
    delete:
      operationId: deleteComposition
      description: Deletes a composition. A scheduled composition is cancelled at the provider first. Blocked once any target has published.
      responses:
        "204": { description: Deleted }
        default: { $ref: "#/components/responses/Error" }
  /compositions/{compositionId}/cancel:
    parameters: [{ name: compositionId, in: path, required: true, schema: { type: string, pattern: '^comp_' } }]
    post:
      operationId: cancelComposition
      description: Unschedules a scheduled composition, cancelling it at the provider and returning it to an editable draft.
      responses:
        "200": { description: The composition, now a draft, content: { application/json: { schema: { $ref: "#/components/schemas/Composition" } } } }
        default: { $ref: "#/components/responses/Error" }
  /compositions/{compositionId}/reschedule:
    parameters: [{ name: compositionId, in: path, required: true, schema: { type: string, pattern: '^comp_' } }]
    post:
      operationId: rescheduleComposition
      description: Changes the time of a scheduled composition. Content is unchanged.
      requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/ScheduleAt" } } } }
      responses:
        "200": { description: Updated Composition truth, content: { application/json: { schema: { $ref: "#/components/schemas/Composition" } } } }
        default: { $ref: "#/components/responses/Error" }
  /compositions/{compositionId}/retry:
    parameters: [{ name: compositionId, in: path, required: true, schema: { type: string, pattern: '^comp_' } }]
    post:
      operationId: retryComposition
      description: Re-submits the retryable failed targets of a partial or failed composition.
      responses:
        "200": { description: Updated Composition truth, content: { application/json: { schema: { $ref: "#/components/schemas/Composition" } } } }
        default: { $ref: "#/components/responses/Error" }
  /compositions/{compositionId}/publish:
    parameters: [{ name: compositionId, in: path, required: true, schema: { type: string, pattern: '^comp_' } }]
    post:
      operationId: publishComposition
      description: Publishes or schedules a draft. Repeating the same intent returns current Composition truth; a different intent conflicts.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - { type: object, additionalProperties: false, required: [at], properties: { at: { const: now } } }
                - { $ref: "#/components/schemas/ScheduleAt" }
      responses:
        "200": { description: Updated Composition truth, content: { application/json: { schema: { $ref: "#/components/schemas/Composition" } } } }
        default: { $ref: "#/components/responses/Error" }
  /analytics:
    get:
      operationId: getAnalytics
      description: >-
        Performance of published content, including posts published outside Get Posting.
        Served from a stored report rather than a live provider call, so it may be a few
        hours old; see freshness. Polling cannot make it update sooner.
      parameters:
        - name: range
          in: query
          schema: { type: string, enum: ["7d", "30d", "90d", "1y"], default: "30d" }
        - name: accountIds
          in: query
          description: Up to 10 account ids. Repeat the parameter or separate with commas. Omit for the whole organization.
          schema: { type: array, maxItems: 10, items: { type: string, pattern: '^acct_' } }
        - name: topPosts
          in: query
          schema: { type: integer, minimum: 0, maximum: 10, default: 5 }
      responses:
        "200": { description: Analytics report, content: { application/json: { schema: { $ref: "#/components/schemas/AnalyticsReport" } } } }
        default: { $ref: "#/components/responses/Error" }
components:
  securitySchemes:
    bearerAuth: { type: http, scheme: bearer, bearerFormat: gp_live }
  parameters:
    Limit: { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 25 } }
    Cursor: { name: cursor, in: query, schema: { type: string } }
  responses:
    Error: { description: API error, content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } } }
  schemas:
    Platform: { type: string, enum: [twitter, instagram, threads, facebook, linkedin, tiktok, youtube] }
    AnalyticsComparison:
      type: object
      additionalProperties: false
      required: [current, previous, change, changePercentage, coverage]
      description: >-
        current is null when no platform in scope reports this metric at all. Null is not
        zero: it means the platform does not publish the number. coverage lists the
        platforms the value accounts for. changePercentage is null when previous was zero.
      properties:
        current: { type: [number, "null"] }
        previous: { type: [number, "null"] }
        change: { type: [number, "null"] }
        changePercentage: { type: [number, "null"] }
        coverage: { type: array, items: { $ref: "#/components/schemas/Platform" } }
    AnalyticsMetrics:
      type: object
      additionalProperties: false
      required: [views, impressions, likes, comments, shares, saves, engagements]
      properties:
        views: { type: integer }
        impressions: { type: integer }
        likes: { type: integer }
        comments: { type: integer }
        shares: { type: integer }
        saves: { type: integer }
        engagements: { type: integer }
    AnalyticsDataset:
      type: object
      additionalProperties: false
      required: [availability, freshness, observedAt, dataAsOf, coverage]
      properties:
        availability: { type: string, enum: [available, partial, pending, unavailable] }
        freshness: { type: string, enum: [fresh, stale, unknown] }
        observedAt: { type: [string, "null"], format: date-time }
        dataAsOf: { type: [string, "null"], format: date-time }
        coverage:
          oneOf:
            - { type: "null" }
            - type: object
              additionalProperties: false
              required: [from, to]
              properties:
                from: { type: string, format: date }
                to: { type: string, format: date }
    AnalyticsTopPost:
      type: object
      additionalProperties: false
      required: [platform, accountId, accountName, accountHandle, content, publishedAt, postUrl, mediaType, syncStatus, metrics]
      properties:
        platform: { $ref: "#/components/schemas/Platform" }
        accountId: { type: [string, "null"], pattern: '^acct_' }
        accountName: { type: string }
        accountHandle: { type: string }
        content: { type: string }
        publishedAt: { type: [string, "null"], format: date-time }
        postUrl: { type: [string, "null"], format: uri }
        mediaType: { type: [string, "null"], enum: [image, video, gif, document, carousel, text, null] }
        syncStatus: { type: string, enum: [synced, pending, unavailable] }
        metrics: { $ref: "#/components/schemas/AnalyticsMetrics" }
    AnalyticsReport:
      type: object
      additionalProperties: false
      required: [range, comparisonRange, scope, summary, platforms, topPosts, insights, freshness, coverage, issues, warnings]
      properties:
        range:
          type: object
          additionalProperties: false
          required: [preset, from, to]
          properties:
            preset: { type: string, enum: ["7d", "30d", "90d", "1y"] }
            from: { type: string, format: date }
            to: { type: string, format: date }
        comparisonRange:
          type: object
          additionalProperties: false
          required: [from, to]
          properties:
            from: { type: string, format: date }
            to: { type: string, format: date }
        scope:
          oneOf:
            - { type: object, additionalProperties: false, required: [kind], properties: { kind: { const: workspace } } }
            - type: object
              additionalProperties: false
              required: [kind, accountIds]
              properties:
                kind: { const: accounts }
                accountIds: { type: array, items: { type: string, pattern: '^acct_' } }
        summary:
          type: object
          additionalProperties: false
          required: [followers, followerGrowth, views, impressions, engagements]
          properties:
            followers: { $ref: "#/components/schemas/AnalyticsComparison" }
            followerGrowth: { $ref: "#/components/schemas/AnalyticsComparison" }
            views: { $ref: "#/components/schemas/AnalyticsComparison" }
            impressions: { $ref: "#/components/schemas/AnalyticsComparison" }
            engagements: { $ref: "#/components/schemas/AnalyticsComparison" }
        platforms:
          type: array
          items:
            type: object
            additionalProperties: false
            required: [platform, availability, datasets, postCount, followers, followerGrowth, metrics]
            properties:
              platform: { $ref: "#/components/schemas/Platform" }
              availability:
                type: string
                enum: [available, partial, pending, unavailable]
                description: Whether this platform's metrics are complete enough to use. Ignore numeric placeholders when unavailable or pending.
              datasets:
                type: object
                additionalProperties: false
                required: [audience, performance]
                properties:
                  audience: { $ref: "#/components/schemas/AnalyticsDataset" }
                  performance: { $ref: "#/components/schemas/AnalyticsDataset" }
              postCount: { type: integer }
              followers: { type: [integer, "null"] }
              followerGrowth: { type: [integer, "null"] }
              metrics: { $ref: "#/components/schemas/AnalyticsMetrics" }
        topPosts: { type: array, items: { $ref: "#/components/schemas/AnalyticsTopPost" } }
        insights:
          type: array
          description: kind is a stable enum; text is human-readable and may be reworded within v1.
          items:
            type: object
            additionalProperties: false
            required: [kind, text]
            properties:
              kind: { type: string, enum: [audienceGrowth, strongestDay, leadingPost] }
              text: { type: string }
        freshness:
          type: object
          additionalProperties: false
          required: [fetchedAt, cache, refreshing]
          properties:
            fetchedAt: { type: string, format: date-time, description: When Get Posting refreshed this report. Platform data time is carried by each dataset. }
            cache: { type: string, enum: [fresh, stale, missing], description: "fresh/stale were served from storage; missing means this request built the report." }
            refreshing: { type: boolean, description: True when newer numbers are already being fetched behind this response. }
        coverage:
          type: object
          additionalProperties: false
          required: [accountCount, audienceAccountCount, performanceAccountCount]
          properties:
            accountCount: { type: integer, minimum: 0 }
            audienceAccountCount: { type: integer, minimum: 0 }
            performanceAccountCount: { type: integer, minimum: 0 }
        issues:
          type: array
          items:
            type: object
            additionalProperties: false
            required: [code, accountId, platform, dataset, message, retryable, nextRetryAt]
            properties:
              code: { type: string, enum: [analytics_initial_observation, analytics_provider_omitted_platform, analytics_access_required, analytics_permission_missing, analytics_spend_cap, analytics_rate_limited, analytics_refresh_failed, analytics_data_gap] }
              accountId: { type: [string, "null"] }
              platform: { $ref: "#/components/schemas/Platform" }
              dataset: { type: string, enum: [audience, performance, report] }
              message: { type: string }
              retryable: { type: boolean }
              nextRetryAt: { type: [string, "null"], format: date-time }
        warnings:
          type: array
          description: Non-empty when part of the report could not be loaded. Treat the report as partial.
          items: { type: string }
    ScheduleAt:
      type: object
      additionalProperties: false
      required: [at]
      properties:
        at: { type: string, pattern: '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?$', description: "Local wall-clock time with no offset or Z, e.g. 2026-08-20T09:30:00. Must resolve to a future instant. Non-existent DST times are rejected; ambiguous DST times use the earlier instant." }
        timeZone: { type: string, description: "Valid IANA time zone name. Defaults to the organization's zone; on reschedule, to the composition's current zone." }
    CompositionStatus: { type: string, enum: [draft, scheduled, publishing, published, partial, failed, cancelled] }
    YouTubeSettingsInput:
      type: object
      additionalProperties: false
      properties:
        title: { type: string }
        visibility: { type: string, enum: [public, private, unlisted] }
        madeForKids: { type: boolean }
        containsSyntheticMedia: { type: boolean }
        categoryId: { type: string }
        firstComment: { type: string, description: "Accepted and stored for compatibility. Get Posting does not currently post a first comment after publish." }
        thumbnailMediaId: { type: string, pattern: '^media_' }
    TikTokSettingsInput:
      type: object
      additionalProperties: false
      properties:
        uploadAsDraft: { type: boolean }
        privacyLevel: { type: string, enum: [PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY] }
        allowComment: { type: boolean }
        allowDuet: { type: boolean }
        allowStitch: { type: boolean }
        contentPreviewConfirmed: { type: boolean }
        expressConsentGiven: { type: boolean }
        containsSyntheticMedia: { type: boolean }
    InstagramSettingsInput:
      type: object
      additionalProperties: false
      properties:
        postType: { type: string, enum: [feed, story, reel] }
        shareToFeed: { type: boolean }
        containsSyntheticMedia: { type: boolean }
        firstComment: { type: string, description: "Accepted and stored for compatibility. Get Posting does not currently post a first comment after publish." }
        audioName: { type: string }
    FacebookSettingsInput:
      type: object
      additionalProperties: false
      properties:
        postType: { type: string, enum: [feed, story, reel] }
        uploadAsDraft: { type: boolean }
        title: { type: string }
        firstComment: { type: string, description: "Accepted and stored for compatibility. Get Posting does not currently post a first comment after publish." }
    LinkedInSettingsInput:
      type: object
      additionalProperties: false
      properties:
        firstComment: { type: string, description: "Accepted and stored for compatibility. Get Posting does not currently post a first comment after publish." }
        disableLinkPreview: { type: boolean }
    ThreadsSettingsInput:
      type: object
      additionalProperties: false
      properties:
        topicTag: { type: string, maxLength: 50, description: "Cannot contain periods or ampersands." }
    ResolvedYouTubeSettings:
      type: object
      additionalProperties: false
      required: [title, visibility, madeForKids, containsSyntheticMedia, categoryId, firstComment, thumbnailMediaId]
      properties:
        title: { type: string }
        visibility: { type: string, enum: [public, private, unlisted] }
        madeForKids: { type: boolean }
        containsSyntheticMedia: { type: boolean }
        categoryId: { type: string }
        firstComment: { type: string, description: "Accepted and stored for compatibility. Get Posting does not currently post a first comment after publish." }
        thumbnailMediaId: { type: [string, 'null'], pattern: '^media_' }
    ResolvedTikTokSettings:
      type: object
      additionalProperties: false
      required: [uploadAsDraft, privacyLevel, allowComment, allowDuet, allowStitch, contentPreviewConfirmed, expressConsentGiven, containsSyntheticMedia]
      properties:
        uploadAsDraft: { type: boolean }
        privacyLevel: { type: string, enum: [PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY] }
        allowComment: { type: boolean }
        allowDuet: { type: boolean }
        allowStitch: { type: boolean }
        contentPreviewConfirmed: { type: boolean }
        expressConsentGiven: { type: boolean }
        containsSyntheticMedia: { type: boolean }
    ResolvedInstagramSettings:
      type: object
      additionalProperties: false
      required: [postType, shareToFeed, containsSyntheticMedia, firstComment, audioName]
      properties:
        postType: { type: string, enum: [feed, story, reel] }
        shareToFeed: { type: boolean }
        containsSyntheticMedia: { type: boolean }
        firstComment: { type: string, description: "Accepted and stored for compatibility. Get Posting does not currently post a first comment after publish." }
        audioName: { type: string }
    ResolvedFacebookSettings:
      type: object
      additionalProperties: false
      required: [postType, uploadAsDraft, title, firstComment]
      properties:
        postType: { type: string, enum: [feed, story, reel] }
        uploadAsDraft: { type: boolean }
        title: { type: string }
        firstComment: { type: string, description: "Accepted and stored for compatibility. Get Posting does not currently post a first comment after publish." }
    ResolvedLinkedInSettings:
      type: object
      additionalProperties: false
      required: [firstComment, disableLinkPreview]
      properties:
        firstComment: { type: string, description: "Accepted and stored for compatibility. Get Posting does not currently post a first comment after publish." }
        disableLinkPreview: { type: boolean }
    ResolvedThreadsSettings:
      type: object
      additionalProperties: false
      required: [topicTag]
      properties:
        topicTag: { type: string }
    SingleContent:
      type: object
      additionalProperties: false
      required: [kind, text, mediaIds]
      properties: { kind: { const: single }, text: { type: string, maxLength: 100000 }, mediaIds: { type: array, maxItems: 35, items: { type: string, pattern: '^media_' } } }
    ThreadItemInput:
      type: object
      additionalProperties: false
      required: [text, mediaIds]
      properties: { id: { type: string, pattern: '^item_' }, text: { type: string, maxLength: 100000 }, mediaIds: { type: array, maxItems: 35, items: { type: string, pattern: '^media_' } } }
    ThreadContent:
      type: object
      additionalProperties: false
      required: [kind, items]
      properties: { kind: { const: thread }, items: { type: array, maxItems: 10, items: { $ref: "#/components/schemas/ThreadItem" } } }
    ThreadItem:
      allOf:
        - { $ref: "#/components/schemas/ThreadItemInput" }
        - { type: object, required: [id] }
    ThreadContentInput:
      type: object
      additionalProperties: false
      required: [kind, items]
      properties: { kind: { const: thread }, items: { type: array, maxItems: 10, items: { $ref: "#/components/schemas/ThreadItemInput" } } }
    ContentInput: { oneOf: [{ $ref: "#/components/schemas/SingleContent" }, { $ref: "#/components/schemas/ThreadContentInput" }] }
    Content: { oneOf: [{ $ref: "#/components/schemas/SingleContent" }, { $ref: "#/components/schemas/ThreadContent" }] }
    TargetWrite:
      type: object
      additionalProperties: false
      required: [accountId]
      properties:
        accountId: { type: string, pattern: '^acct_' }
        content: { $ref: "#/components/schemas/ContentInput" }
        settings:
          anyOf:
            - { $ref: "#/components/schemas/YouTubeSettingsInput" }
            - { $ref: "#/components/schemas/TikTokSettingsInput" }
            - { $ref: "#/components/schemas/InstagramSettingsInput" }
            - { $ref: "#/components/schemas/FacebookSettingsInput" }
            - { $ref: "#/components/schemas/LinkedInSettingsInput" }
            - { $ref: "#/components/schemas/ThreadsSettingsInput" }
    CompositionWrite:
      type: object
      additionalProperties: false
      required: [content]
      properties: { content: { $ref: "#/components/schemas/ContentInput" }, targets: { type: array, maxItems: 100, items: { $ref: "#/components/schemas/TargetWrite" } } }
    CompositionPatch:
      type: object
      additionalProperties: false
      minProperties: 1
      properties: { content: { $ref: "#/components/schemas/ContentInput" }, targets: { type: array, maxItems: 100, items: { $ref: "#/components/schemas/TargetWrite" } } }
    ValidationMessage:
      type: object
      required: [code, message, source]
      properties: { code: { type: string }, message: { type: string }, path: { type: string }, platform: { $ref: "#/components/schemas/Platform" }, accountId: { type: string }, mediaId: { type: string }, itemId: { type: string }, limit: { type: number }, actual: { type: number }, source: { type: string, enum: [get_posting, provider] } }
    Validation:
      type: object
      required: [valid, errors, warnings]
      properties: { valid: { type: boolean }, errors: { type: array, items: { $ref: "#/components/schemas/ValidationMessage" } }, warnings: { type: array, items: { $ref: "#/components/schemas/ValidationMessage" } } }
    Account:
      type: object
      required: [id, platform, name, handle, avatarUrl, status, canPost]
      properties:
        id: { type: string, pattern: '^acct_' }
        platform: { $ref: "#/components/schemas/Platform" }
        name: { type: string }
        handle: { type: string }
        avatarUrl: { type: [string, 'null'], format: uri }
        status: { type: string, enum: [connected, disconnected, unhealthy] }
        canPost: { type: boolean }
        defaultSettings:
          oneOf:
            - { $ref: "#/components/schemas/ResolvedYouTubeSettings" }
            - { $ref: "#/components/schemas/ResolvedTikTokSettings" }
            - { $ref: "#/components/schemas/ResolvedInstagramSettings" }
            - { $ref: "#/components/schemas/ResolvedFacebookSettings" }
            - { $ref: "#/components/schemas/ResolvedLinkedInSettings" }
            - { $ref: "#/components/schemas/ResolvedThreadsSettings" }
    Media:
      type: object
      required: [id, type, name, url, mimeType, size, width, height, durationMs, status, createdAt, updatedAt]
      properties: { id: { type: string, pattern: '^media_' }, type: { type: string, enum: [image, video] }, name: { type: string }, url: { type: string, format: uri }, mimeType: { type: string }, size: { type: number }, width: { type: [number, 'null'] }, height: { type: [number, 'null'] }, durationMs: { type: [number, 'null'] }, status: { type: string, enum: [uploading, ready, failed] }, createdAt: { type: string, format: date-time }, updatedAt: { type: string, format: date-time } }
    Target:
      type: object
      required: [id, accountId, platform, resolved, status, postUrl, error]
      properties:
        id: { type: string, pattern: '^target_' }
        accountId: { type: string, pattern: '^acct_' }
        platform: { $ref: "#/components/schemas/Platform" }
        content: { $ref: "#/components/schemas/Content" }
        settings:
          anyOf:
            - { $ref: "#/components/schemas/YouTubeSettingsInput" }
            - { $ref: "#/components/schemas/TikTokSettingsInput" }
            - { $ref: "#/components/schemas/InstagramSettingsInput" }
            - { $ref: "#/components/schemas/FacebookSettingsInput" }
            - { $ref: "#/components/schemas/LinkedInSettingsInput" }
            - { $ref: "#/components/schemas/ThreadsSettingsInput" }
        resolved:
          type: object
          required: [content, settings]
          properties:
            content: { $ref: "#/components/schemas/Content" }
            settings:
              oneOf:
                - { $ref: "#/components/schemas/ResolvedYouTubeSettings" }
                - { $ref: "#/components/schemas/ResolvedTikTokSettings" }
                - { $ref: "#/components/schemas/ResolvedInstagramSettings" }
                - { $ref: "#/components/schemas/ResolvedFacebookSettings" }
                - { $ref: "#/components/schemas/ResolvedLinkedInSettings" }
                - { $ref: "#/components/schemas/ResolvedThreadsSettings" }
                - { type: object, additionalProperties: false, maxProperties: 0 }
        status: { type: string, enum: [draft, scheduled, publishing, published, failed, cancelled] }
        postUrl: { type: [string, 'null'], format: uri }
        error:
          oneOf:
            - { $ref: "#/components/schemas/TargetError" }
            - { type: 'null' }
    TargetError:
      type: object
      required: [code, message, retryable, source]
      properties:
        code: { type: string, enum: [provider_rejected, target_failed] }
        message: { type: string }
        retryable: { type: boolean }
        source: { type: string, enum: [get_posting, provider] }
    AuditActor:
      type: object
      required: [type, id, name]
      properties:
        type: { type: string, enum: [user, apiKey] }
        id: { type: string, pattern: '^(user|key)_' }
        name: { type: string }
    Composition:
      type: object
      required: [id, status, content, targets, scheduledAt, timeZone, validation, createdAt, updatedAt, createdVia, createdBy, dashboardUrl]
      properties: { id: { type: string, pattern: '^comp_' }, status: { $ref: "#/components/schemas/CompositionStatus" }, content: { $ref: "#/components/schemas/Content" }, targets: { type: array, items: { $ref: "#/components/schemas/Target" } }, scheduledAt: { type: [string, 'null'], format: date-time }, timeZone: { type: [string, 'null'] }, validation: { $ref: "#/components/schemas/Validation" }, createdAt: { type: string, format: date-time }, updatedAt: { type: string, format: date-time }, createdVia: { type: string, enum: [app, api, mcp] }, createdBy: { $ref: "#/components/schemas/AuditActor" }, submittedVia: { type: string, enum: [app, api, mcp] }, submittedBy: { $ref: "#/components/schemas/AuditActor" }, dashboardUrl: { type: string, format: uri } }
    CompositionSummary:
      type: object
      required: [id, status, contentPreview, scheduledAt, timeZone, targetCount, validation, createdAt, updatedAt]
      properties:
        id: { type: string, pattern: '^comp_' }
        status: { $ref: "#/components/schemas/CompositionStatus" }
        contentPreview: { type: string }
        scheduledAt: { type: [string, 'null'], format: date-time }
        timeZone: { type: [string, 'null'] }
        targetCount: { type: integer, minimum: 0 }
        validation:
          type: object
          required: [valid, errorCount, warningCount]
          properties:
            valid: { type: boolean }
            errorCount: { type: integer, minimum: 0 }
            warningCount: { type: integer, minimum: 0 }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }
    AccountCollection: { type: object, required: [data, nextCursor], properties: { data: { type: array, items: { $ref: "#/components/schemas/Account" } }, nextCursor: { type: [string, 'null'] } } }
    MediaCollection: { type: object, required: [data, nextCursor], properties: { data: { type: array, items: { $ref: "#/components/schemas/Media" } }, nextCursor: { type: [string, 'null'] } } }
    CompositionCollection: { type: object, required: [data, nextCursor], properties: { data: { type: array, items: { $ref: "#/components/schemas/CompositionSummary" } }, nextCursor: { type: [string, 'null'] } } }
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message, requestId]
          properties:
            code: { type: string, enum: [invalid_request, invalid_draft, unauthorized, forbidden, not_found, invalid_state, validation_failed, payload_too_large, unsupported_media_type, rate_limited, outcome_unknown, internal_error] }
            message: { type: string }
            details: { type: object }
            requestId: { type: string, pattern: '^req_' }
