Everpop Campaigns API

The Campaigns API from Everpop — 3 operation(s) for campaigns.

GitHub OpenAPI

Operations 4

GET /api/v1/campaigns List campaigns with post counts, measured views and the public payout-report… #
POST /api/v1/campaigns Create a paid-clipping campaign (Pro plan or above) #
POST /api/v1/campaigns/{id}/attach Attach the user's published posts to a campaign (a post counts toward one… #
GET /api/v1/campaigns/{id}/proof A campaign's measured payout report: per-clip 48h/7d numbers, totals, estimated… #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/everpop-app:everpop-app-campaigns-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

everpop-app-campaigns-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Everpop Campaigns API
  version: 1.3.1
  description: 'Turn your own videos into published Shorts for YouTube, Instagram, Facebook and TikTok, with measured receipts on eligible YouTube Shorts. Built to be driven by AI agents: upload a file, clip it, publish it, and pull real 48h/7d performance — all by API. REST and MCP need the Pro or Scale plan; other accounts get HTTP 402 with reason subscription (no active subscription) or plan (the plan doesn''t include API access). Full agent guide: https://everpop.app/skill.md · MCP connector: https://everpop.app/api/mcp/mcp (OAuth, or Authorization: Bearer epk_...)'
  termsOfService: https://everpop.app/terms
  x-changelog:
  - version: 1.3.1
    date: '2026-10-16'
    changes: 'Changed: when no natural sentence end fits a clip, it can now end on a held final frame, flagged open_ending in qcWarnings (one whose hold could not land is flagged mid_sentence_end); both wait for review and never auto-post. A render whose clips all end that way hands its video back, so GET /api/v1/usage does not count it. A video gets one uncharged render a month, shared with repeat-only renders, whichever came first.'
  - version: 1.3.0
    date: '2026-10-08'
    changes: 'Changed: POST /api/v1/clip on a video whose strong moments were all clipped already completed FAILED with nothing new to clip; it now completes with one clip that reuses the best of them, flagged repeat_moment in qcWarnings. The first such render of a video in a month hands its video back, so GET /api/v1/usage does not count it. Automatic renders keep the old answer.'
  - version: 1.2.0
    date: '2026-10-01'
    changes: 'Changed: the upload size limit was 4 GB and is now 32 GB. POST /api/v1/videos/upload answered 413 file_too_large for a file over 4 GB; it now takes a file up to 5 GB, the most one PUT can carry. A file over 5 GB and up to 32 GB answers 413 file_too_large with reason single_put and browserUploadUrl: the user uploads it in the browser. A file over 32 GB still answers 413 file_too_large. Added: browserUploadUrl on that error body.'
  - version: 1.1.0
    date: '2026-09-24'
    changes: 'Changed: a 401 answered code unauthorized and now names its case: key_missing, key_invalid or key_expired. The Instagram posting-safety cap answered 429 rate_limited and now answers posting_cap. A 429 sent Retry-After: 60 whatever its cause; it now fits the cause (the seconds until resetsAt for quota_exhausted, 180 for queue_capacity, none for upload_cleanup_backlog, upload_retention_backlog and posting_cap, which carry retryable: false). A method a route does not support answered an empty 405; it now answers 405 method_not_allowed with an Allow header. Added: error bodies document their detail fields and a list of known codes (ErrorCode), including meta_publish_frozen. GET /api/v1/posts takes clipId. generateShort is marked deprecated while paused. Examples on the upload, finalize, clip, job, publish and receipts operations.'
  - version: 1.0.0
    date: '2026-07-03'
    changes: First published spec.
  contact:
    email: support@everpop.app
    url: https://everpop.app/contact
servers:
- url: https://everpop.app
security:
- apiKey: []
tags:
- name: Campaigns
paths:
  /api/v1/campaigns:
    get:
      operationId: listCampaigns
      summary: List campaigns with post counts, measured views and the public payout-report…
      responses:
        '200':
          description: The account's campaigns with their post rollup and public report link.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignListEnvelope'
        '402':
          description: reason=plan — campaigns are not included in this account's plan.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      tags:
      - Campaigns
    post:
      operationId: createCampaign
      summary: Create a paid-clipping campaign (Pro plan or above)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - name
              properties:
                name:
                  type: string
                  minLength: 2
                  maxLength: 120
                sourceVideoId:
                  type: string
                  minLength: 1
                  maxLength: 64
                  description: The sponsor's footage in the user's library; posts published from its clips attach automatically.
                sourceUrl:
                  type: string
                  format: uri
                  maxLength: 500
                  description: Link to the brief (stored, never fetched).
                cpmCents:
                  type: integer
                  minimum: 0
                  maximum: 100000
                budgetCents:
                  type: integer
                  minimum: 0
                  maximum: 1000000000
                deadline:
                  type: string
                  format: date-time
                notes:
                  type: string
                  maxLength: 2000
                brief:
                  type: object
                  description: Structured brief rules enforced on every attach.
      responses:
        '200':
          description: The created campaign.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignEnvelope'
        '402':
          description: Subscription required, or reason=plan.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '404':
          description: sourceVideoId is not in the user's library.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '422':
          description: The body did not validate, the brief could not be read, or both brief and notes were sent.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
      tags:
      - Campaigns
  /api/v1/campaigns/{id}/attach:
    post:
      operationId: attachToCampaign
      summary: Attach the user's published posts to a campaign (a post counts toward one…
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - postIds
              properties:
                postIds:
                  type: array
                  minItems: 1
                  maxItems: 100
                  items:
                    type: string
                    minLength: 1
                  description: postId values from GET /api/v1/receipts or the campaign proof.
      responses:
        '200':
          description: How many posts were attached.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AttachEnvelope'
        '403':
          description: A post id is not one of the user's published posts (named in the message).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '404':
          description: Campaign not found for this account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '422':
          description: A post is not published yet, a post breaks the campaign's brief rules or the brief cannot be read, or the body did not validate.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '429':
          $ref: '#/components/responses/RateLimited'
      tags:
      - Campaigns
  /api/v1/campaigns/{id}/proof:
    get:
      operationId: campaignProof
      summary: 'A campaign''s measured payout report: per-clip 48h/7d numbers, totals, estimated…'
      parameters:
      - name: id
        in: path
        required: true
        schema:
          type: string
      - name: csv
        in: query
        required: false
        schema:
          type: string
        description: Any value returns a CSV download instead of JSON.
      responses:
        '200':
          description: The payout report as JSON, or with ?csv a CSV download (one row per post).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignProofEnvelope'
            text/csv:
              schema:
                type: string
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: Campaign not found for this account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '429':
          $ref: '#/components/responses/RateLimited'
      tags:
      - Campaigns
components:
  schemas:
    CampaignEnvelope:
      type: object
      properties:
        ok:
          const: true
        data:
          type: object
          properties:
            campaign:
              $ref: '#/components/schemas/Campaign'
          required:
          - campaign
      required:
      - ok
      - data
    ErrorCode:
      type: string
      description: 'Codes an agent branches on, each explained in https://everpop.app/skill.md (Error codes). The list grows additively: handle a code that is not listed by its HTTP status.'
      enum:
      - bad_request
      - unauthorized
      - key_missing
      - key_invalid
      - key_expired
      - payment_required
      - subscription
      - plan
      - forbidden
      - scope
      - suspended
      - disabled
      - content_review
      - not_found
      - method_not_allowed
      - conflict
      - idempotency_conflict
      - clip_removed
      - upload_not_ready
      - file_too_large
      - unprocessable
      - validation_failed
      - invalid_cursor
      - upload_required
      - generated_source
      - demo_source
      - unknown_destination
      - no_eligible_destination
      - clip_not_ready
      - rights_unconfirmed
      - rate_limited
      - quota_exhausted
      - queue_capacity
      - upload_slots
      - upload_cleanup_backlog
      - upload_retention_backlog
      - posting_cap
      - internal_error
      - upstream_error
      - unavailable
      - engine_offline
      - meta_publish_frozen
      - cleanup_pending
    CampaignProofEnvelope:
      type: object
      properties:
        ok:
          const: true
        data:
          type: object
          properties:
            campaign:
              type: object
              properties:
                id:
                  type: string
                name:
                  type: string
                sourceUrl:
                  type:
                  - string
                  - 'null'
                sourceVideoId:
                  type:
                  - string
                  - 'null'
                sourceVideoTitle:
                  type:
                  - string
                  - 'null'
                platformRule:
                  type: string
                cpmCents:
                  type:
                  - integer
                  - 'null'
                budgetCents:
                  type:
                  - integer
                  - 'null'
                deadline:
                  type:
                  - string
                  - 'null'
                  format: date-time
                status:
                  type: string
                  enum:
                  - ACTIVE
                  - COMPLETED
              required:
              - id
              - name
              - sourceUrl
              - sourceVideoId
              - sourceVideoTitle
              - platformRule
              - cpmCents
              - budgetCents
              - deadline
              - status
            briefLines:
              type: array
              items:
                type: string
              description: The brief rules every attached post passed.
            items:
              type: array
              items:
                $ref: '#/components/schemas/ProofItem'
              description: The campaign's published posts, newest first.
            totals:
              type: object
              properties:
                posts:
                  type: integer
                views48h:
                  type: number
                views7d:
                  type: number
                verifiedViews:
                  type: number
                  description: 'Measured views: per post the 7-day window when measured, else 48h.'
              required:
              - posts
              - views48h
              - views7d
              - verifiedViews
            estimatedPayoutCents:
              type:
              - integer
              - 'null'
              description: totals.verifiedViews ÷ 1,000 × cpmCents, rounded; null without a CPM. An estimate — the marketplace's own view count governs payout.
            bundleUrl:
              type:
              - string
              - 'null'
              format: uri
              description: One token-protected link to the measured receipts; null when none is measured.
            publicUrl:
              type:
              - string
              - 'null'
              format: uri
              description: The token-protected public payout-report page.
          required:
          - campaign
          - briefLines
          - items
          - totals
          - estimatedPayoutCents
          - bundleUrl
          - publicUrl
      required:
      - ok
      - data
    ErrorEnvelope:
      type: object
      properties:
        ok:
          const: false
        error:
          type: string
          description: Human-readable; safe to show the user.
        reason:
          type: string
          description: 'Kept for compatibility where it existed (e.g. subscription, plan, scope, unknown_destination, content_review). With code=file_too_large, single_put: the file is over 5 GB, the most one PUT can carry, and within the 32 GB limit.'
        code:
          anyOf:
          - $ref: '#/components/schemas/ErrorCode'
          - type: string
          description: 'Stable machine code, on every error: the specific code (e.g. upload_required, quota_exhausted, invalid_cursor, file_too_large), else the reason, else by status (bad_request, not_found, rate_limited, internal_error, …). Branch on this.'
        retryable:
          type: boolean
          description: 'On some 429s. false (quota_exhausted, upload_cleanup_backlog, upload_retention_backlog, posting_cap): waiting a minute will not help; stop and tell the user. true (queue_capacity): retry after Retry-After.'
        issues:
          type: object
          description: 'With code=validation_failed: the field errors ({ formErrors, fieldErrors }).'
        used:
          type: integer
          description: 'With code=quota_exhausted: videos (or AI shorts) used in the current window.'
        limit:
          type: integer
          description: 'With code=quota_exhausted: the plan''s monthly allowance.'
        resetsAt:
          type: string
          format: date-time
          description: 'With code=quota_exhausted: when the allowance resets.'
        scope:
          type: string
          enum:
          - videos
          - generations
          - user
          - global
          description: 'With code=quota_exhausted: which allowance (videos or generations). With code=queue_capacity: whose queue is full (user or global).'
        destinationIds:
          type: array
          items:
            type: string
          description: 'With code=unknown_destination: the requested ids that are not connected and healthy.'
        uploadUrl:
          type: string
          format: uri
          description: 'With code=upload_required: the sign-in-gated page where the user uploads this video''s file.'
        browserUploadUrl:
          type: string
          format: uri
          description: 'With code=file_too_large and reason=single_put: the sign-in-gated page where the user uploads the file (up to 32 GB).'
      required:
      - ok
      - error
      - code
    ProofItem:
      type: object
      properties:
        postId:
          type: string
        title:
          type:
          - string
          - 'null'
        publishedUrl:
          type:
          - string
          - 'null'
        postedAt:
          type:
          - string
          - 'null'
          format: date-time
        outcomes:
          type: object
          properties:
            h48:
              oneOf:
              - $ref: '#/components/schemas/ProofWindow'
              - type: 'null'
            d7:
              oneOf:
              - $ref: '#/components/schemas/ProofWindow'
              - type: 'null'
          required:
          - h48
          - d7
        platform:
          type: string
          enum:
          - TIKTOK
          - INSTAGRAM_REELS
          - YOUTUBE_SHORTS
          - FACEBOOK_REELS
          - THREADS
          description: Only YouTube Shorts are measured; posts on other platforms carry no outcomes.
        learnExclude:
          type: boolean
          description: 'true when the post is excluded from learning: by the creator, or automatically when the Short went private or was deleted on YouTube, or because it is a Weekly Pop. Outcome capture skips it, so it gets no new measured windows.'
        receiptUrl:
          type:
          - string
          - 'null'
          format: uri
      required:
      - postId
      - title
      - publishedUrl
      - postedAt
      - outcomes
      - platform
      - learnExclude
      - receiptUrl
    CampaignListItem:
      type: object
      description: Every Campaign field, plus the rollup of the campaign's published posts.
      properties:
        id:
          type: string
        userId:
          type: string
        name:
          type: string
        sourceUrl:
          type:
          - string
          - 'null'
        sourceVideoId:
          type:
          - string
          - 'null'
        platformRule:
          type: string
        cpmCents:
          type:
          - integer
          - 'null'
        budgetCents:
          type:
          - integer
          - 'null'
        deadline:
          type:
          - string
          - 'null'
          format: date-time
        notes:
          type:
          - string
          - 'null'
        status:
          type: string
          enum:
          - ACTIVE
          - COMPLETED
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        postCount:
          type: integer
          description: Published posts attached to the campaign.
        verifiedViews:
          type: number
          description: 'Measured YouTube Analytics views summed over the attached posts: per post the 7-day window when measured, else 48h (so a lower bound while 48h counts still climb).'
        measuredCount:
          type: integer
          description: Attached posts with at least one measured window.
        platformCounts:
          type: object
          additionalProperties:
            type: integer
          description: Attached posts per platform.
        estimatedPayoutCents:
          type:
          - integer
          - 'null'
          description: verifiedViews ÷ 1,000 × cpmCents, rounded; null without a CPM. An estimate — the marketplace's own view count governs payout.
        publicUrl:
          type:
          - string
          - 'null'
          format: uri
          description: The token-protected public payout-report page.
        source:
          oneOf:
          - type: object
            properties:
              id:
                type: string
              title:
                type: string
              status:
                type: string
              rightsOk:
                type: boolean
                description: Whether publishing from this footage is allowed (the user's rights attestation is on file).
            required:
            - id
            - title
            - status
            - rightsOk
          - type: 'null'
        brief:
          type:
          - object
          - 'null'
          description: The structured brief rules enforced on every attach, or null.
        briefLines:
          type: array
          items:
            type: string
          description: The brief rules as plain sentences.
        briefUnreadable:
          type: boolean
          description: true when stored brief rules could not be read (attaching refuses until they are fixed).
      required:
      - id
      - userId
      - name
      - sourceUrl
      - sourceVideoId
      - platformRule
      - cpmCents
      - budgetCents
      - deadline
      - notes
      - status
      - createdAt
      - updatedAt
      - postCount
      - verifiedViews
      - measuredCount
      - platformCounts
      - estimatedPayoutCents
      - publicUrl
      - source
      - brief
      - briefLines
      - briefUnreadable
    PaywallError:
      type: object
      properties:
        ok:
          const: false
        error:
          type: string
        reason:
          type: string
          enum:
          - subscription
          - plan
        code:
          type: string
          enum:
          - subscription
          - plan
          description: The same value as reason.
      required:
      - ok
      - error
      - reason
      - code
    ProofWindow:
      type: object
      properties:
        views:
          type:
          - number
          - 'null'
        averageViewPercentage:
          type:
          - number
          - 'null'
        likes:
          type:
          - number
          - 'null'
        subscribersGained:
          type:
          - number
          - 'null'
        measuredAt:
          type: string
          format: date-time
      required:
      - views
      - averageViewPercentage
      - likes
      - subscribersGained
      - measuredAt
    CampaignListEnvelope:
      type: object
      properties:
        ok:
          const: true
        data:
          type: object
          properties:
            campaigns:
              type: array
              items:
                $ref: '#/components/schemas/CampaignListItem'
              description: Newest first.
          required:
          - campaigns
      required:
      - ok
      - data
    AttachEnvelope:
      type: object
      properties:
        ok:
          const: true
        data:
          type: object
          properties:
            attached:
              type: integer
              description: Distinct posts attached (repeated ids count once).
          required:
          - attached
      required:
      - ok
      - data
    Campaign:
      type: object
      description: A campaign row as stored.
      properties:
        id:
          type: string
          description: The id /api/v1/campaigns/{id}/attach and /proof take.
        userId:
          type: string
          description: The owner (the calling account).
        name:
          type: string
        sourceUrl:
          type:
          - string
          - 'null'
          description: Link to the sponsor's brief — stored, never fetched.
        sourceVideoId:
          type:
          - string
          - 'null'
          description: The sponsor's footage in the user's library; posts published from its clips attach automatically.
        platformRule:
          type: string
        cpmCents:
          type:
          - integer
          - 'null'
        budgetCents:
          type:
          - integer
          - 'null'
        deadline:
          type:
          - string
          - 'null'
          format: date-time
        notes:
          type:
          - string
          - 'null'
          description: Free-text notes, or the stored form of structured brief rules (read brief / briefLines on GET /api/v1/campaigns instead).
        status:
          type: string
          enum:
          - ACTIVE
          - COMPLETED
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
      required:
      - id
      - userId
      - name
      - sourceUrl
      - sourceVideoId
      - platformRule
      - cpmCents
      - budgetCents
      - deadline
      - notes
      - status
      - createdAt
      - updatedAt
  responses:
    Forbidden:
      description: 'code=scope (also reason=scope): the API key is read-only and this is a write. code=suspended: the account is suspended. Endpoint-specific refusals document their own reason.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    PaymentRequired:
      description: 'REST and MCP need the Pro or Scale plan. reason=subscription: the account has no active subscription (subscribe, or redeem an invite code). reason=plan: the account''s plan doesn''t include this (API and agent access need Pro or Scale).'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/PaywallError'
    Unauthorized:
      description: 'The API key did not authenticate. code=key_missing: no Authorization: Bearer epk_... header. code=key_invalid: the key is unknown or revoked. code=key_expired: the key passed its expiry; the user creates a new one in Dashboard → API.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    RateLimited:
      description: 'Too many requests. code=rate_limited: the per-minute limit; wait Retry-After (60) seconds. code=quota_exhausted (with used, limit, resetsAt, retryable: false): the monthly allowance is used up; Retry-After is the seconds until resetsAt — tell the user rather than wait. code=queue_capacity (retryable: true): the render queue is full; retry after Retry-After (180).'
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
  headers:
    RetryAfter:
      description: 'Seconds to wait before retrying: 60 for a per-minute rate limit (rate_limited, upload_slots), 180 for queue_capacity, the seconds until resetsAt for quota_exhausted. Absent on upload_cleanup_backlog, upload_retention_backlog and posting_cap, where waiting will not help soon.'
      schema:
        type: integer
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: epk_...
      description: Per-user API key from everpop.app/dashboard/api (Dashboard → API). Write scope required for mutating endpoints.