openapi: 3.1.0
info:
  title: Passionfroot Creator API
  version: 1.0.0-alpha
  description: |
    Access a creator's own partners, collaborations, placements, catalog, proposals, and conversations.
    The API is in alpha. Every operation is restricted to the creator account that owns the API key.
servers:
  - url: https://workspace.passionfroot.me/api/creators/v1
security:
  - bearerAuth: []
tags:
  - name: Placements
  - name: Partners
  - name: Collaborations
  - name: Conversations
  - name: Catalog
  - name: Requests
  - name: Proposals
paths:
  /placements:
    get:
      tags: [Placements]
      summary: List placements
      description: Returns the creator's scheduled deliverables. All prices are creator earnings after Passionfroot fees and before taxes.
      operationId: listCreatorPlacements
      x-required-scope: creator:read:collaborations
      parameters:
        - { $ref: "#/components/parameters/StartDate" }
        - { $ref: "#/components/parameters/EndDate" }
        - { $ref: "#/components/parameters/MetricsUpdatedAfter" }
        - { $ref: "#/components/parameters/MetricsUpdatedBefore" }
        - name: collaborationId
          in: query
          description: Return placements belonging to this collaboration.
          schema: { type: string }
        - { $ref: "#/components/parameters/Status" }
        - name: include
          in: query
          description: Comma-separated expansions. Supported values are `partner` and `collaboration`.
          schema: { type: string }
          example: partner,collaboration
        - { $ref: "#/components/parameters/Limit250" }
        - { $ref: "#/components/parameters/Cursor" }
      responses:
        "200":
          description: A page of creator placements.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CreatorPlacementPage" }
              example:
                data:
                  - id: plc_123
                    partnerId: partner_xyz
                    collaborationId: collab_xyz
                    date: "2026-09-15T00:00:00.000Z"
                    name: Acme launch – Newsletter feature
                    status: confirmed
                    platform: newsletter
                    postType: Dedicated
                    url: https://example.com/acme-launch
                    viewCount: 15000
                    likeCount: 1200
                    commentCount: 45
                    shareCount: 30
                    metricsLastUpdatedAt: "2026-09-20T14:30:00.000Z"
                    priceCents: 142500
                    usdPriceCents: 142500
                    currency: USD
                    partner: { id: partner_xyz, name: Acme Corp, websiteUrl: "https://acme.com" }
                pagination: { nextCursor: plc_122, hasMore: true }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/ServerError" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
  /partners:
    get:
      tags: [Partners]
      summary: List partners
      description: Returns brands with which the creator has a qualifying collaboration.
      operationId: listCreatorPartners
      x-required-scope: creator:read:collaborations
      parameters:
        - { $ref: "#/components/parameters/Limit100" }
        - { $ref: "#/components/parameters/Cursor" }
      responses:
        "200":
          description: A page of partners.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/PartnerPage" }
              example:
                data: [{ id: partner_xyz, name: Acme Corp, websiteUrl: "https://acme.com" }]
                pagination: { nextCursor: partner_abc, hasMore: true }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/ServerError" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
  /partners/{partnerId}:
    get:
      tags: [Partners]
      summary: Get a partner
      description: Returns one partner when the creator has a qualifying collaboration with it.
      operationId: getCreatorPartner
      x-required-scope: creator:read:collaborations
      parameters:
        - { $ref: "#/components/parameters/PartnerId" }
      responses:
        "200": { $ref: "#/components/responses/Partner" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/ServerError" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
  /collaborations:
    get:
      tags: [Collaborations]
      summary: List collaborations
      description: Returns the creator's collaborations. Prices are creator earnings, not the amount paid by the brand.
      operationId: listCreatorCollaborations
      x-required-scope: creator:read:collaborations
      parameters:
        - { $ref: "#/components/parameters/Status" }
        - name: partnerId
          in: query
          description: Return collaborations with this partner.
          schema: { type: string }
        - { $ref: "#/components/parameters/Limit250" }
        - { $ref: "#/components/parameters/Cursor" }
      responses:
        "200":
          description: A page of collaborations.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CreatorCollaborationPage" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/ServerError" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
  /collaborations/{collaborationId}:
    get:
      tags: [Collaborations]
      summary: Get a collaboration
      description: Returns one collaboration owned by the creator.
      operationId: getCreatorCollaboration
      x-required-scope: creator:read:collaborations
      parameters:
        - { $ref: "#/components/parameters/CollaborationId" }
      responses:
        "200": { $ref: "#/components/responses/CreatorCollaboration" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/ServerError" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
  /conversations:
    get:
      tags: [Conversations]
      summary: List conversations
      description: Returns conversations ordered by most recent message activity. Deactivated partners are excluded.
      operationId: listCreatorConversations
      x-required-scope: creator:read:conversations
      parameters:
        - { $ref: "#/components/parameters/UpdatedAfter" }
        - name: partnerId
          in: query
          description: Return the conversation with this partner, if one exists.
          schema: { type: string }
        - { $ref: "#/components/parameters/IsUnread" }
        - { $ref: "#/components/parameters/IsArchived" }
        - { $ref: "#/components/parameters/IsBlocked" }
        - { $ref: "#/components/parameters/Search" }
        - name: include
          in: query
          description: Set to `partner` to include partner details.
          schema: { type: string, enum: [partner] }
        - { $ref: "#/components/parameters/Limit250" }
        - { $ref: "#/components/parameters/Cursor" }
      responses:
        "200":
          description: A page of conversations.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CreatorConversationPage" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/ServerError" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
  /conversations/{conversationId}/messages:
    get:
      tags: [Conversations]
      summary: List conversation messages
      description: Returns the complete typed timeline newest first. Reading has no side effects. A blocked conversation's timeline is unavailable until it is unblocked in the inbox.
      operationId: listCreatorConversationMessages
      x-required-scope: creator:read:conversations
      parameters:
        - { $ref: "#/components/parameters/ConversationId" }
        - { $ref: "#/components/parameters/CreatedAfter" }
        - { $ref: "#/components/parameters/Limit250" }
        - { $ref: "#/components/parameters/Cursor" }
      responses:
        "200":
          description: A page of timeline events.
          content:
            application/json:
              schema: { $ref: "#/components/schemas/MessagePage" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/ServerError" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
    post:
      tags: [Conversations]
      summary: Send a message
      description: Sends a text message to the partner and marks the conversation read for the creator.
      operationId: sendCreatorConversationMessage
      x-required-scope: creator:write:conversations
      parameters:
        - { $ref: "#/components/parameters/ConversationId" }
        - { $ref: "#/components/parameters/IdempotencyKey" }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/MessageInput" }
            example: { text: "Thanks! The draft will be ready by Friday." }
      responses:
        "201": { $ref: "#/components/responses/SentMessage" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "422": { $ref: "#/components/responses/UnprocessableEntity" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/ServerError" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
  /conversations/{conversationId}/read:
    post:
      tags: [Conversations]
      summary: Mark a conversation as read
      description: Marks activity up to the latest observed message as read without changing `lastActivityAt`. Naturally idempotent.
      operationId: markCreatorConversationRead
      x-required-scope: creator:write:conversations
      parameters:
        - { $ref: "#/components/parameters/ConversationId" }
      responses:
        "200": { $ref: "#/components/responses/CreatorConversation" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/ServerError" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
  /products:
    get:
      tags: [Catalog]
      summary: List products
      description: Returns the creator's complete storefront-visible standalone product catalog. This endpoint is not paginated.
      operationId: listCreatorProducts
      x-required-scope: creator:read:collaborations
      responses:
        "200":
          description: Storefront-visible products.
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  { data: { type: array, items: { $ref: "#/components/schemas/Product" } } }
              example:
                data:
                  - id: prod_abc
                    name: Dedicated Newsletter
                    channel: { id: chan_xyz, title: The Weekly Brief, platformType: newsletter }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/ServerError" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
  /packages:
    get:
      tags: [Catalog]
      summary: List packages
      description: Returns the complete storefront-visible, priced package catalog. This endpoint is not paginated.
      operationId: listCreatorPackages
      x-required-scope: creator:read:collaborations
      responses:
        "200":
          description: Storefront-visible packages.
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  { data: { type: array, items: { $ref: "#/components/schemas/Package" } } }
              example:
                data:
                  - id: pkg_abc
                    name: Newsletter + Social Bundle
                    priceCents: 250000
                    pricingModel: fixed_package
                    currency: EUR
                    products: [{ productId: prod_abc, name: Dedicated Newsletter, quantity: 1 }]
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/ServerError" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
  /requests/{requestId}/accept:
    post:
      tags: [Requests]
      summary: Accept a package request
      description: Accepts the brand's package request as-is and books the requested dates. An empty body is valid. Naturally idempotent.
      operationId: acceptCreatorRequest
      x-required-scope: creator:write:proposals
      parameters:
        - { $ref: "#/components/parameters/RequestId" }
      requestBody:
        required: false
        content:
          application/json:
            schema: { $ref: "#/components/schemas/AcceptRequestInput" }
            example: { autoInvoice: true, daysUntilInvoiceDue: 30, requestAssets: true }
      responses:
        "200": { $ref: "#/components/responses/RequestState" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/ServerError" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
  /requests/{requestId}/decline:
    post:
      tags: [Requests]
      summary: Decline a package request
      description: Declines the request and sends the reason to the brand. Naturally idempotent.
      operationId: declineCreatorRequest
      x-required-scope: creator:write:proposals
      parameters:
        - { $ref: "#/components/parameters/RequestId" }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required: [comment]
              properties:
                reason: { type: string, enum: [timing, brand_fit, other], default: other }
                comment: { type: string, minLength: 1, maxLength: 10000 }
            example:
              {
                reason: timing,
                comment: "I do not have availability during the requested launch window.",
              }
      responses:
        "200": { $ref: "#/components/responses/RequestState" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/ServerError" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
  /proposals:
    post:
      tags: [Proposals]
      summary: Create a proposal
      description: Replies to a brand request with the creator's own products, dates, and list price. `basePriceCents` is denominated in the collaboration currency and is not the creator payout returned by read endpoints.
      operationId: createCreatorProposal
      x-required-scope: creator:write:proposals
      parameters:
        - { $ref: "#/components/parameters/IdempotencyKey" }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreateProposalInput" }
            example:
              requestId: req_abc
              packageId: pkg_abc
              placements:
                - { productId: prod_abc, date: "2026-09-14" }
                - { productId: prod_abc, date: "2026-09-28" }
              basePriceCents: 250000
              comment: Both slots are open.
      responses:
        "201": { $ref: "#/components/responses/IdempotentProposal" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "422": { $ref: "#/components/responses/UnprocessableEntity" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/ServerError" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
  /proposals/{proposalId}/counter:
    post:
      tags: [Proposals]
      summary: Counter a proposal
      description: Cancels the pending proposal and replaces it with a new proposal on the same collaboration.
      operationId: counterCreatorProposal
      x-required-scope: creator:write:proposals
      parameters:
        - { $ref: "#/components/parameters/ProposalId" }
        - { $ref: "#/components/parameters/IdempotencyKey" }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CounterProposalInput" }
      responses:
        "201": { $ref: "#/components/responses/IdempotentProposal" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "422": { $ref: "#/components/responses/UnprocessableEntity" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/ServerError" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
  /proposals/{proposalId}/accept:
    post:
      tags: [Proposals]
      summary: Accept a brand proposal
      description: Accepts a pending brand-authored proposal and books its dates. Naturally idempotent.
      operationId: acceptCreatorProposal
      x-required-scope: creator:write:proposals
      parameters:
        - { $ref: "#/components/parameters/ProposalId" }
      responses:
        "200": { $ref: "#/components/responses/Proposal" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/ServerError" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
  /proposals/{proposalId}/reject:
    post:
      tags: [Proposals]
      summary: Reject a brand proposal
      description: Rejects the proposal, cancels the collaboration, and releases its dates. Send a separate conversation message if the brand needs a reason. Naturally idempotent.
      operationId: rejectCreatorProposal
      x-required-scope: creator:write:proposals
      parameters:
        - { $ref: "#/components/parameters/ProposalId" }
      responses:
        "200": { $ref: "#/components/responses/Proposal" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/Forbidden" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "500": { $ref: "#/components/responses/ServerError" }
        "503": { $ref: "#/components/responses/ServiceUnavailable" }
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Passionfroot API key
      description: Create an API key from Settings → API in an approved Creator workspace.
  parameters:
    Cursor:
      {
        name: cursor,
        in: query,
        description: Opaque cursor returned by the previous page.,
        schema: { type: string },
      }
    Limit250:
      { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 250, default: 50 } }
    Limit100:
      { name: limit, in: query, schema: { type: integer, minimum: 1, maximum: 100, default: 50 } }
    StartDate:
      {
        name: startDate,
        in: query,
        description: Include placements on or after this date.,
        schema: { type: string, format: date },
      }
    EndDate:
      {
        name: endDate,
        in: query,
        description: Include placements on or before this date.,
        schema: { type: string, format: date },
      }
    MetricsUpdatedAfter:
      {
        name: metricsUpdatedAfter,
        in: query,
        description: Include placements whose reported metrics were updated on or after this date or timestamp.,
        schema: { type: string },
      }
    MetricsUpdatedBefore:
      {
        name: metricsUpdatedBefore,
        in: query,
        description: Include placements whose reported metrics were updated on or before this date or timestamp.,
        schema: { type: string },
      }
    Status:
      {
        name: status,
        in: query,
        description: "Comma-separated statuses.",
        schema: { type: string },
        example: "confirmed,pending",
      }
    UpdatedAfter:
      {
        name: updatedAfter,
        in: query,
        description: Include conversations with message activity on or after this date or timestamp.,
        schema: { type: string },
      }
    CreatedAfter:
      {
        name: createdAfter,
        in: query,
        description: Include messages created on or after this date or timestamp.,
        schema: { type: string },
      }
    IsUnread: { name: isUnread, in: query, schema: { type: boolean } }
    IsArchived: { name: isArchived, in: query, schema: { type: boolean } }
    IsBlocked: { name: isBlocked, in: query, schema: { type: boolean } }
    Search:
      {
        name: q,
        in: query,
        description: Case-insensitive search across partner names and message text.,
        schema: { type: string, minLength: 2, maxLength: 200 },
      }
    PartnerId: { name: partnerId, in: path, required: true, schema: { type: string } }
    CollaborationId: { name: collaborationId, in: path, required: true, schema: { type: string } }
    ConversationId: { name: conversationId, in: path, required: true, schema: { type: string } }
    RequestId: { name: requestId, in: path, required: true, schema: { type: string } }
    ProposalId: { name: proposalId, in: path, required: true, schema: { type: string } }
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      description: Unique key for one logical write. Reuse the key and identical body for retries.
      schema: { type: string, minLength: 1, maxLength: 255 }
  headers:
    IdempotencyReplayed:
      description: Present with the value `true` when this response was replayed from a previous request.
      schema: { type: string, const: "true" }
    RetryAfter:
      { description: Seconds to wait before retrying., schema: { type: integer, minimum: 1 } }
  responses:
    Partner:
      description: The partner.
      content:
        {
          application/json:
            {
              schema:
                {
                  type: object,
                  required: [data],
                  properties: { data: { $ref: "#/components/schemas/Partner" } },
                },
            },
        }
    CreatorCollaboration:
      description: The collaboration.
      content:
        {
          application/json:
            {
              schema:
                {
                  type: object,
                  required: [data],
                  properties: { data: { $ref: "#/components/schemas/CreatorCollaboration" } },
                },
            },
        }
    CreatorConversation:
      description: The updated conversation.
      content:
        {
          application/json:
            {
              schema:
                {
                  type: object,
                  required: [data],
                  properties: { data: { $ref: "#/components/schemas/CreatorConversation" } },
                },
            },
        }
    SentMessage:
      description: Message sent.
      headers:
        Idempotency-Replayed: { $ref: "#/components/headers/IdempotencyReplayed" }
      content:
        application/json:
          schema:
            {
              type: object,
              required: [data],
              properties: { data: { $ref: "#/components/schemas/SentMessage" } },
            }
          example:
            {
              data:
                { id: msg_abc, conversationId: conv_abc, createdAt: "2026-09-01T09:20:00.000Z" },
            }
    RequestState:
      description: The request's current state.
      content:
        application/json:
          schema:
            {
              type: object,
              required: [data],
              properties: { data: { $ref: "#/components/schemas/RequestState" } },
            }
          example: { data: { requestId: req_abc, collaborationId: collab_abc, status: accepted } }
    Proposal:
      description: The proposal's current state.
      content:
        {
          application/json:
            {
              schema:
                {
                  type: object,
                  required: [data],
                  properties: { data: { $ref: "#/components/schemas/Proposal" } },
                },
            },
        }
    IdempotentProposal:
      description: The proposal's current state.
      headers:
        Idempotency-Replayed: { $ref: "#/components/headers/IdempotencyReplayed" }
      content:
        application/json:
          schema:
            type: object
            required: [data]
            properties: { data: { $ref: "#/components/schemas/Proposal" } }
    BadRequest:
      {
        description: "Invalid parameters or body.",
        content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } },
      }
    Unauthorized:
      {
        description: "Missing, invalid, expired, or revoked API key.",
        content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } },
      }
    Forbidden:
      {
        description: "The key lacks access or the required scope.",
        content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } },
      }
    NotFound:
      {
        description: "Resource not found or not owned by this creator.",
        content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } },
      }
    Conflict:
      {
        description: "The operation conflicts with current state or an identical write is in progress.",
        content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } },
      }
    UnprocessableEntity:
      {
        description: "The body is valid JSON but references an invalid catalog item or reuses an idempotency key with different input.",
        content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } },
      }
    TooManyRequests:
      description: Rate limit exceeded.
      headers: { Retry-After: { $ref: "#/components/headers/RetryAfter" } }
      content:
        {
          application/json:
            {
              schema: { $ref: "#/components/schemas/Error" },
              example:
                {
                  error:
                    {
                      code: RATE_LIMITED,
                      message: "This API key is over its rate limit. Wait for Retry-After before sending more.",
                      details: { limit: 2, windowSeconds: 1 },
                    },
                },
            },
        }
    ServerError:
      {
        description: "Unexpected server failure. Re-read state before retrying a write.",
        content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } },
      }
    ServiceUnavailable:
      description: Temporary key-verification or write-lock infrastructure failure. Retry after the indicated delay.
      headers: { Retry-After: { $ref: "#/components/headers/RetryAfter" } }
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
  schemas:
    Error:
      type: object
      additionalProperties: false
      required: [error]
      description: The body of every error response. Branch on `code`; messages can be reworded.
      properties:
        error:
          type: object
          additionalProperties: false
          required: [code, message]
          properties:
            code:
              type: string
              description: Stable, machine-readable reason. New codes may be added; existing ones never change meaning.
              enum:
                [
                  INVALID_REQUEST,
                  INVALID_QUERY_PARAMETER,
                  INVALID_BODY,
                  INVALID_ID,
                  INVALID_CURSOR,
                  INVALID_IDEMPOTENCY_KEY,
                  UNAUTHENTICATED,
                  MISSING_SCOPE,
                  NOT_FOUND,
                  METHOD_NOT_ALLOWED,
                  CONFLICT,
                  IDEMPOTENCY_IN_PROGRESS,
                  UNPROCESSABLE,
                  IDEMPOTENCY_KEY_REUSED,
                  RATE_LIMITED,
                  INTERNAL_ERROR,
                  SERVICE_UNAVAILABLE,
                ]
            message:
              type: string
              description: What went wrong and what to do next, written for a developer or agent.
            details:
              description: Present only when there is something structured to act on, such as one entry per invalid field (`{ field, message }`).
    Pagination:
      type: object
      required: [hasMore]
      properties: { nextCursor: { type: [string, "null"] }, hasMore: { type: boolean } }
    Partner:
      type: object
      required: [id, name, websiteUrl]
      properties:
        id: { type: string }
        name: { type: string }
        websiteUrl: { type: [string, "null"], format: uri }
    CreatorCollaboration:
      type: object
      required:
        [
          id,
          name,
          partnerId,
          status,
          priceCents,
          usdPriceCents,
          currency,
          createdAt,
          statusUpdatedAt,
        ]
      properties:
        id: { type: string }
        name: { type: string }
        partnerId: { type: string }
        status: { type: string, enum: [pending, confirmed, cancelled] }
        priceCents:
          {
            type: [integer, "null"],
            description: Creator earnings after Passionfroot fees and before taxes.,
          }
        usdPriceCents: { type: [integer, "null"] }
        currency: { type: string }
        createdAt: { type: string, format: date-time }
        statusUpdatedAt: { type: string, format: date-time }
    CreatorPlacement:
      type: object
      required:
        [
          id,
          partnerId,
          collaborationId,
          date,
          name,
          status,
          platform,
          postType,
          url,
          viewCount,
          likeCount,
          commentCount,
          shareCount,
          metricsLastUpdatedAt,
          priceCents,
          usdPriceCents,
          currency,
        ]
      properties:
        id: { type: string }
        partnerId: { type: [string, "null"] }
        collaborationId: { type: [string, "null"] }
        date: { type: string, format: date-time }
        name: { type: string }
        status: { type: string, enum: [pending, confirmed, cancelled] }
        platform: { type: [string, "null"] }
        postType: { type: [string, "null"] }
        url: { type: [string, "null"], format: uri }
        viewCount: { type: [integer, "null"] }
        likeCount: { type: [integer, "null"] }
        commentCount: { type: [integer, "null"] }
        shareCount: { type: [integer, "null"] }
        metricsLastUpdatedAt: { type: [string, "null"], format: date-time }
        priceCents:
          {
            type: [integer, "null"],
            description: Creator earnings for the placement after Passionfroot fees and before taxes.,
          }
        usdPriceCents: { type: [integer, "null"] }
        currency: { type: [string, "null"] }
        partner: { anyOf: [{ $ref: "#/components/schemas/Partner" }, { type: "null" }] }
        collaboration:
          { anyOf: [{ $ref: "#/components/schemas/CreatorCollaboration" }, { type: "null" }] }
    CreatorConversation:
      type: object
      required: [id, partnerId, isUnread, isArchived, isBlocked, lastActivityAt, createdAt]
      properties:
        id: { type: string }
        partnerId: { type: string }
        isUnread: { type: boolean }
        isArchived: { type: boolean }
        isBlocked: { type: boolean }
        lastActivityAt: { type: string, format: date-time }
        createdAt: { type: string, format: date-time }
        partner: { $ref: "#/components/schemas/Partner" }
    Attachment:
      type: object
      required: [fileName, fileSize, mimeType, downloadUrl]
      properties:
        fileName: { type: string }
        fileSize: { type: integer }
        mimeType: { type: string }
        downloadUrl:
          {
            type: [string, "null"],
            format: uri,
            description: Signed URL that expires after one hour.,
          }
    ProposalPlacement:
      type: object
      required: [date, postType, platform]
      properties:
        date: { type: string }
        postType: { type: [string, "null"] }
        platform: { type: [string, "null"] }
    Proposal:
      type: object
      required:
        [
          proposalId,
          collaborationId,
          requestId,
          status,
          createdBy,
          priceCents,
          budgetCents,
          currency,
          packageName,
          placements,
        ]
      properties:
        proposalId: { type: [string, "null"] }
        collaborationId: { type: string }
        requestId: { type: string }
        status: { type: string, enum: [offer_pending, pending, accepted, rejected, cancelled] }
        createdBy: { type: string, enum: [creator, brand] }
        priceCents:
          {
            type: [integer, "null"],
            description: Creator earnings if accepted. Mutually exclusive with `budgetCents`.,
          }
        budgetCents:
          {
            type: [integer, "null"],
            description: Brand's disclosed gross budget. Mutually exclusive with `priceCents`.,
          }
        currency: { type: [string, "null"] }
        packageName: { type: string }
        comment: { type: string }
        placements: { type: array, items: { $ref: "#/components/schemas/ProposalPlacement" } }
    Message:
      type: object
      required: [id, type, actor, collaborationId, createdAt]
      properties:
        id: { type: string }
        type: { type: string, description: Public timeline event type. }
        actor: { type: string, enum: [creator, brand, system] }
        collaborationId: { type: [string, "null"] }
        createdAt: { type: string, format: date-time }
        text: { type: string, description: May contain basic HTML. }
        comment: { type: string }
        attachments: { type: array, items: { $ref: "#/components/schemas/Attachment" } }
        proposal: { anyOf: [{ $ref: "#/components/schemas/Proposal" }, { type: "null" }] }
        stats:
          type: object
          properties:
            viewCount: { type: [integer, "null"] }
            likeCount: { type: [integer, "null"] }
            commentCount: { type: [integer, "null"] }
            shareCount: { type: [integer, "null"] }
            contentUrl: { type: [string, "null"] }
            contentTitle: { type: [string, "null"] }
            platform: { type: [string, "null"] }
    MessageInput:
      type: object
      additionalProperties: false
      required: [text]
      properties:
        {
          text:
            {
              type: string,
              minLength: 1,
              maxLength: 10000,
              description: Plain text or allowed basic HTML.,
            },
        }
    SentMessage:
      type: object
      required: [id, conversationId, createdAt]
      properties:
        {
          id: { type: string },
          conversationId: { type: string },
          createdAt: { type: string, format: date-time },
        }
    Product:
      type: object
      required: [id, name, channel]
      properties:
        id: { type: string }
        name: { type: string }
        channel:
          type: object
          required: [id, title, platformType]
          properties:
            { id: { type: string }, title: { type: string }, platformType: { type: string } }
    Package:
      type: object
      required: [id, name, priceCents, pricingModel, currency, products]
      properties:
        id: { type: string }
        name: { type: string }
        priceCents: { type: integer }
        pricingModel: { type: string, enum: [fixed_package, per_placement] }
        currency: { type: string }
        products:
          type: array
          items:
            type: object
            required: [productId, name, quantity]
            properties:
              {
                productId: { type: string },
                name: { type: string },
                quantity: { type: integer, minimum: 1 },
              }
    AcceptRequestInput:
      type: object
      additionalProperties: false
      properties:
        autoInvoice: { type: boolean }
        daysUntilInvoiceDue:
          {
            type: integer,
            minimum: 1,
            maximum: 365,
            description: Required when `autoInvoice` is true.,
          }
        requestAssets: { type: boolean, default: false }
        forfeitPendingProposal:
          {
            type: boolean,
            default: false,
            description: Must be true to accept the original request while the creator's own proposal is pending.,
          }
    RequestState:
      type: object
      required: [requestId, collaborationId, status]
      properties:
        requestId: { type: string }
        collaborationId: { type: string }
        status: { type: string, enum: [accepted, rejected] }
    ProposalPlacementInput:
      type: object
      additionalProperties: false
      required: [productId, date]
      properties:
        productId: { type: string }
        date: { type: string, description: "YYYY-MM-DD or an ISO 8601 timestamp with offset." }
    ProposalTermsInput:
      type: object
      required: [placements, basePriceCents]
      properties:
        placements:
          {
            type: array,
            minItems: 1,
            maxItems: 100,
            items: { $ref: "#/components/schemas/ProposalPlacementInput" },
          }
        basePriceCents:
          {
            type: integer,
            minimum: 1,
            maximum: 2147483647,
            description: Creator list price for the entire proposal in the collaboration currency.,
          }
        packageId: { type: string }
        name: { type: string }
        comment: { type: string }
        autoInvoice: { type: boolean }
        daysUntilInvoiceDue: { type: integer, minimum: 1, maximum: 365 }
    CreateProposalInput:
      unevaluatedProperties: false
      allOf:
        - { $ref: "#/components/schemas/ProposalTermsInput" }
        - type: object
          required: [requestId]
          properties: { requestId: { type: string } }
    CounterProposalInput:
      unevaluatedProperties: false
      allOf:
        - { $ref: "#/components/schemas/ProposalTermsInput" }
    CreatorPlacementPage:
      type: object
      required: [data, pagination]
      properties:
        {
          data: { type: array, items: { $ref: "#/components/schemas/CreatorPlacement" } },
          pagination: { $ref: "#/components/schemas/Pagination" },
        }
    PartnerPage:
      type: object
      required: [data, pagination]
      properties:
        {
          data: { type: array, items: { $ref: "#/components/schemas/Partner" } },
          pagination: { $ref: "#/components/schemas/Pagination" },
        }
    CreatorCollaborationPage:
      type: object
      required: [data, pagination]
      properties:
        {
          data: { type: array, items: { $ref: "#/components/schemas/CreatorCollaboration" } },
          pagination: { $ref: "#/components/schemas/Pagination" },
        }
    CreatorConversationPage:
      type: object
      required: [data, pagination]
      properties:
        {
          data: { type: array, items: { $ref: "#/components/schemas/CreatorConversation" } },
          pagination: { $ref: "#/components/schemas/Pagination" },
        }
    MessagePage:
      type: object
      required: [data, pagination]
      properties:
        {
          data: { type: array, items: { $ref: "#/components/schemas/Message" } },
          pagination: { $ref: "#/components/schemas/Pagination" },
        }
