{
  "openapi": "3.1.0",
  "info": {
    "title": "clips API",
    "version": "1.0.0",
    "summary": "Launch clip campaigns, take submissions and pay clippers per view in USDC, from your own code.",
    "description": "The clips API does everything the website does: brands launch and fund campaigns, follow submissions and payouts, and get webhooks; clippers submit clips and read their earnings. Money is never held by the API: every call that moves USDC returns a Solana transaction for your wallet to sign. Guides at https://clipz.family/docs.",
    "contact": {
      "name": "clips",
      "url": "https://clipz.family/docs"
    }
  },
  "servers": [
    {
      "url": "https://clipz.family/api",
      "description": "Production (Solana mainnet)"
    }
  ],
  "security": [],
  "tags": [
    {
      "name": "Campaigns",
      "description": "Create, fund, edit, stop and read campaigns."
    },
    {
      "name": "Clips",
      "description": "Clips, submissions and the clipper’s own account."
    },
    {
      "name": "Transactions",
      "description": "Relay a transaction your wallet signed."
    },
    {
      "name": "Uploads",
      "description": "Videos and logos for a campaign."
    },
    {
      "name": "Webhooks",
      "description": "Signed event notifications."
    },
    {
      "name": "Keys",
      "description": "API keys and the signed challenge that creates them."
    },
    {
      "name": "Platform",
      "description": "Settings and totals."
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ck_live_… / ck_test_…",
        "description": "An API key, sent as `Authorization: Bearer ck_live_…`. Keys act for the wallet that created them."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "description": "The kind of error.",
                "enum": [
                  "invalid_request_error",
                  "authentication_error",
                  "permission_error",
                  "not_found",
                  "conflict",
                  "rate_limit_error",
                  "api_error"
                ]
              },
              "code": {
                "type": "string",
                "description": "A stable, machine-readable code, e.g. `parameter_invalid`."
              },
              "message": {
                "type": "string",
                "description": "What went wrong, for people."
              },
              "param": {
                "type": "string",
                "description": "The parameter at fault, when there is one."
              },
              "requestId": {
                "type": "string",
                "description": "Quote it when you contact us."
              }
            },
            "required": [
              "type",
              "code",
              "message",
              "requestId"
            ]
          }
        },
        "required": [
          "error"
        ]
      },
      "Campaign": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The campaign’s address on Solana; its id everywhere."
          },
          "object": {
            "const": "campaign"
          },
          "url": {
            "type": "string",
            "description": "Its page on the website."
          },
          "status": {
            "type": "string",
            "description": "`live` takes clips; `ended` is past its end (clips posted in time are still paid until `withdrawAfter`, for the views they had by the end); `closed` once the funder took back the rest.",
            "enum": [
              "live",
              "ended",
              "closed"
            ]
          },
          "title": {
            "type": "string",
            "description": "Name."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pitch shown to clippers."
          },
          "imageUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cover image."
          },
          "logoUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "The logo every clip must show, when the campaign has one."
          },
          "funder": {
            "type": "string",
            "description": "The wallet that funds the campaign."
          },
          "mint": {
            "type": "string",
            "description": "The token it pays in (USDC)."
          },
          "ratePer1k": {
            "type": "number",
            "description": "USDC paid per 1,000 verified views."
          },
          "maxPerClip": {
            "type": "number",
            "description": "The most one clip can earn, USDC."
          },
          "maxPerDay": {
            "type": [
              "number",
              "null"
            ],
            "description": "The most the campaign pays out per UTC day, USDC, set by the funder (`POST /v1/campaigns/{id}/cap`); null: no cap. Views past it stay owed and are paid on the following days."
          },
          "budget": {
            "type": "object",
            "properties": {
              "funded": {
                "type": "number",
                "description": "Deposited, after the fee."
              },
              "paid": {
                "type": "number",
                "description": "Paid to clippers so far (at once and vesting)."
              },
              "remaining": {
                "type": "number",
                "description": "Left to pay out."
              },
              "vesting": {
                "type": "number",
                "description": "Paid but not yet released to clippers."
              }
            },
            "required": [
              "funded",
              "paid",
              "remaining",
              "vesting"
            ]
          },
          "viewsRemaining": {
            "type": "integer",
            "description": "Views the remaining budget pays for."
          },
          "startAt": {
            "type": "string",
            "description": "Clips published from here count.",
            "format": "date-time"
          },
          "endAt": {
            "type": "string",
            "description": "No clips published after this count.",
            "format": "date-time"
          },
          "withdrawAfter": {
            "type": "string",
            "description": "From here the funder can take back what is left.",
            "format": "date-time"
          },
          "platforms": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "tiktok",
                "instagram",
                "youtube",
                "x"
              ]
            },
            "description": "The platforms whose posts the campaign takes (all four unless the brand chose)."
          },
          "audience": {
            "type": "object",
            "properties": {
              "countries": {
                "type": "array",
                "items": {
                  "type": "string",
                  "description": "ISO 3166 country code."
                },
                "description": "Where the audience must be; empty means anywhere."
              },
              "minShare": {
                "type": "number",
                "description": "With countries: at least this share (0.01 to 1) of an account’s audience, as the platform reports it, must be in them. Accounts below are not accepted."
              },
              "allowUnknown": {
                "type": "boolean",
                "description": "With countries: whether accounts whose platform reports no audience location (X, smaller TikTok and Instagram accounts) are accepted. False: they are not."
              },
              "minFollowers": {
                "type": "integer",
                "description": "Accounts need at least this many followers."
              }
            },
            "required": [
              "countries",
              "minShare",
              "allowUnknown",
              "minFollowers"
            ]
          },
          "brief": {
            "$ref": "#/components/schemas/Brief"
          },
          "tag": {
            "type": "string",
            "description": "Older campaigns only: posts carrying this tag are taken without a submission."
          },
          "assets": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Video name."
                },
                "url": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "ready": {
                  "type": "boolean",
                  "description": "False while the logo is being burned in."
                }
              },
              "required": [
                "name",
                "url",
                "ready"
              ]
            },
            "description": "Uploaded source videos (with the logo burned in when there is one)."
          },
          "clips": {
            "type": "integer",
            "description": "Clips counting."
          },
          "isFunder": {
            "type": "boolean",
            "description": "Whether the caller funds this campaign."
          }
        },
        "required": [
          "id",
          "object",
          "url",
          "status",
          "title",
          "description",
          "imageUrl",
          "logoUrl",
          "funder",
          "mint",
          "ratePer1k",
          "maxPerClip",
          "maxPerDay",
          "budget",
          "viewsRemaining",
          "startAt",
          "endAt",
          "withdrawAfter",
          "platforms",
          "audience",
          "brief",
          "assets",
          "clips",
          "isFunder"
        ]
      },
      "Brief": {
        "type": "object",
        "properties": {
          "content": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string",
                  "description": "Shown to clippers."
                },
                "url": {
                  "type": "string",
                  "description": "An https link: a stream, video, folder or sheet."
                }
              },
              "required": [
                "label",
                "url"
              ]
            },
            "description": "What to clip."
          },
          "clipRequirements": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "One requirement."
            },
            "description": "What every clip must show or do; checked on the video’s frames."
          },
          "captionRequirements": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "One requirement."
            },
            "description": "What every caption must contain; checked on the caption."
          },
          "violations": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "One violation."
            },
            "description": "Anything here disqualifies a clip. Defaults to a standard list."
          }
        },
        "required": [
          "content",
          "clipRequirements",
          "captionRequirements",
          "violations"
        ],
        "description": "The brief clippers work from; every submitted clip is checked against it."
      },
      "CampaignInput": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Name, up to 80 characters."
          },
          "description": {
            "type": "string",
            "description": "Pitch, up to 600 characters."
          },
          "content": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string",
                  "description": "e.g. \"Stream\"."
                },
                "url": {
                  "type": "string",
                  "description": "https link."
                }
              },
              "required": [
                "url"
              ]
            },
            "description": "What to clip. At least one link, or `videos`."
          },
          "videos": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "An upload id from POST /v1/uploads."
            },
            "description": "Source videos to offer clippers (max 10)."
          },
          "videoNames": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "Name per video."
            },
            "description": "Optional names for `videos`."
          },
          "logo": {
            "type": "string",
            "description": "An upload id (PNG, JPEG, WebP). Every clip must show it; uploaded videos get it burned in."
          },
          "clipRequirements": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "One requirement."
            },
            "description": "Up to 12."
          },
          "captionRequirements": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "One requirement."
            },
            "description": "Up to 12."
          },
          "violations": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "One violation."
            },
            "description": "Up to 12; omit for the standard list."
          },
          "platforms": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "tiktok",
                "instagram",
                "youtube",
                "x"
              ]
            },
            "description": "Only take posts from these platforms. Omit for all four."
          },
          "audienceCountries": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "ISO 3166 alpha-2, e.g. \"US\"."
            },
            "description": "Only pay accounts whose audience is here, as the platform reports it (Instagram, YouTube, TikTok). Omit for anywhere."
          },
          "minAudienceShare": {
            "type": "number",
            "description": "With `audienceCountries`: the share of an account’s audience that must be in them, 0.01 to 1.",
            "default": 0.5
          },
          "allowUnknownAudience": {
            "type": "boolean",
            "default": false,
            "description": "With `audienceCountries`: also accept accounts whose platform does not report where their audience is (all of X; TikTok under 2,000 followers; Instagram under 100). Off by default: those accounts cannot prove the audience you pay for."
          },
          "minFollowers": {
            "type": "integer",
            "description": "Accounts need at least this many followers.",
            "default": 0
          },
          "imageUrl": {
            "type": "string",
            "description": "https cover image."
          },
          "budget": {
            "type": "number",
            "description": "USDC to deposit (the fee is taken from it).",
            "minimum": 1
          },
          "ratePer1k": {
            "type": "number",
            "description": "USDC per 1,000 views.",
            "exclusiveMinimum": 0
          },
          "maxPerClip": {
            "type": "number",
            "description": "Most one clip can earn, USDC. Defaults to 10% of the budget."
          },
          "maxPerDay": {
            "type": "number",
            "description": "Optional: the most the campaign pays per UTC day, USDC (at least 0.01). It bounds what a fault or a stolen oracle key could pay out in a day; deposit ÷ days × 2 leaves room for a good day. Set in the same transaction; change it later with `POST /v1/campaigns/{id}/cap`. Not available while `GET /v1/config` says `dailyCaps: false`."
          },
          "durationDays": {
            "type": "integer",
            "description": "How long clips can be posted, 1 to 365.",
            "default": 30
          },
          "endAt": {
            "type": "string",
            "description": "Instead of `durationDays`: the end, ISO 8601.",
            "format": "date-time"
          }
        },
        "required": [
          "title",
          "budget",
          "ratePer1k"
        ]
      },
      "CampaignPatch": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Name."
          },
          "description": {
            "type": "string",
            "description": "Pitch."
          },
          "imageUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "https cover image, or null to remove it."
          },
          "content": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string",
                  "description": ""
                },
                "url": {
                  "type": "string",
                  "description": ""
                }
              },
              "required": [
                "url"
              ]
            },
            "description": "Replaces the content links."
          },
          "clipRequirements": {
            "type": "array",
            "items": {
              "type": "string",
              "description": ""
            },
            "description": "Replaces them; applies to clips checked from now."
          },
          "captionRequirements": {
            "type": "array",
            "items": {
              "type": "string",
              "description": ""
            },
            "description": "Replaces them; applies to clips checked from now."
          },
          "violations": {
            "type": "array",
            "items": {
              "type": "string",
              "description": ""
            },
            "description": "Replaces them."
          },
          "platforms": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "tiktok",
                "instagram",
                "youtube",
                "x"
              ]
            },
            "description": "Replaces them; applies to submissions from now."
          },
          "audienceCountries": {
            "type": "array",
            "items": {
              "type": "string",
              "description": ""
            },
            "description": "Replaces them; applies to clips accepted from now."
          },
          "minAudienceShare": {
            "type": "number",
            "description": "0.01 to 1."
          },
          "allowUnknownAudience": {
            "type": "boolean",
            "description": ""
          },
          "minFollowers": {
            "type": "integer",
            "description": ""
          },
          "addVideos": {
            "type": "array",
            "items": {
              "type": "string",
              "description": ""
            },
            "description": "Upload ids of more source videos, added to the clip kit after the ones it has (the logo is burned in as at launch). Ids already in the kit are skipped; the kit holds at most 20."
          },
          "addVideoNames": {
            "type": "array",
            "items": {
              "type": "string",
              "description": ""
            },
            "description": "Optional names for `addVideos`, in the same order."
          }
        },
        "required": [],
        "description": "Only the fields you send change. Money, rate and dates live on chain and cannot be edited."
      },
      "Transaction": {
        "type": "object",
        "properties": {
          "encoding": {
            "const": "base64"
          },
          "transaction": {
            "type": "string",
            "description": "The serialized, unsigned Solana transaction. Deserialize it (`Transaction.from(Buffer.from(tx, \"base64\"))`), sign it with `signer`, and send it back or submit it yourself."
          },
          "lastValidBlockHeight": {
            "type": "integer",
            "description": "The transaction expires after this block height (about a minute). Ask for a new one if it does."
          },
          "signer": {
            "type": "string",
            "description": "The wallet that must sign; it is also the fee payer."
          }
        },
        "required": [
          "encoding",
          "transaction",
          "lastValidBlockHeight",
          "signer"
        ]
      },
      "TransactionRequest": {
        "type": "object",
        "properties": {
          "object": {
            "const": "transaction_request"
          },
          "action": {
            "type": "string",
            "description": "",
            "enum": [
              "fund",
              "stop",
              "withdraw",
              "set_cap"
            ]
          },
          "campaign": {
            "type": "string",
            "description": ""
          },
          "amount": {
            "type": "number",
            "description": "USDC moved, where it applies."
          },
          "maxPerDay": {
            "type": [
              "number",
              "null"
            ],
            "description": "`set_cap`: the new daily cap, USDC; null removes it."
          },
          "fee": {
            "type": "number",
            "description": "Fee taken from a deposit."
          },
          "transaction": {
            "$ref": "#/components/schemas/Transaction"
          }
        },
        "required": [
          "object",
          "action",
          "campaign",
          "transaction"
        ]
      },
      "TransactionResult": {
        "type": "object",
        "properties": {
          "object": {
            "const": "transaction"
          },
          "signature": {
            "type": "string",
            "description": "The transaction signature."
          },
          "explorer": {
            "type": "string",
            "description": "A Solana explorer link."
          }
        },
        "required": [
          "object",
          "signature",
          "explorer"
        ]
      },
      "Upload": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Upload id, for `videos` or `logo`."
          },
          "object": {
            "const": "upload"
          },
          "contentType": {
            "type": "string",
            "description": ""
          },
          "url": {
            "type": "string",
            "description": "Where it is served."
          }
        },
        "required": [
          "id",
          "object",
          "contentType",
          "url"
        ]
      },
      "Challenge": {
        "type": "object",
        "properties": {
          "object": {
            "const": "challenge"
          },
          "wallet": {
            "type": "string",
            "description": ""
          },
          "message": {
            "type": "string",
            "description": "Sign exactly this text."
          },
          "expiresAt": {
            "type": "string",
            "description": "",
            "format": "date-time"
          }
        },
        "required": [
          "object",
          "wallet",
          "message",
          "expiresAt"
        ]
      },
      "Stats": {
        "type": "object",
        "properties": {
          "object": {
            "const": "stats"
          },
          "paid": {
            "type": "number",
            "description": "USDC paid to clippers."
          },
          "liveCampaigns": {
            "type": "integer",
            "description": ""
          },
          "clips": {
            "type": "integer",
            "description": ""
          },
          "clippers": {
            "type": "integer",
            "description": ""
          },
          "views": {
            "type": "integer",
            "description": ""
          }
        },
        "required": [
          "object",
          "paid",
          "liveCampaigns",
          "clips",
          "clippers",
          "views"
        ]
      },
      "Deleted": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": ""
          },
          "object": {
            "type": "string",
            "description": ""
          },
          "deleted": {
            "const": true
          }
        },
        "required": [
          "id",
          "object",
          "deleted"
        ]
      },
      "WebhookTest": {
        "type": "object",
        "properties": {
          "object": {
            "const": "webhook_test"
          },
          "event": {
            "type": [
              "string",
              "null"
            ]
          },
          "delivered": {
            "type": "boolean",
            "description": ""
          },
          "responseStatus": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "object",
          "event",
          "delivered",
          "responseStatus",
          "error"
        ]
      },
      "CampaignDraft": {
        "type": "object",
        "properties": {
          "object": {
            "const": "campaign_draft"
          },
          "campaign": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The address the campaign will have."
              },
              "object": {
                "const": "campaign"
              },
              "url": {
                "type": "string",
                "description": ""
              },
              "status": {
                "const": "awaiting_signature"
              },
              "title": {
                "type": "string",
                "description": ""
              },
              "funder": {
                "type": "string",
                "description": ""
              },
              "mint": {
                "type": "string",
                "description": ""
              },
              "ratePer1k": {
                "type": "number",
                "description": ""
              },
              "maxPerClip": {
                "type": "number",
                "description": ""
              },
              "maxPerDay": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "deposit": {
                "type": "number",
                "description": ""
              },
              "endAt": {
                "type": "string",
                "description": "",
                "format": "date-time"
              }
            },
            "required": [
              "id",
              "object",
              "url",
              "status",
              "title",
              "funder",
              "mint",
              "ratePer1k",
              "maxPerClip",
              "maxPerDay",
              "deposit",
              "endAt"
            ]
          },
          "fee": {
            "type": "number",
            "description": "USDC taken from the deposit."
          },
          "transaction": {
            "$ref": "#/components/schemas/Transaction"
          },
          "next": {
            "type": "string",
            "description": "What to do next."
          }
        },
        "required": [
          "object",
          "campaign",
          "fee",
          "transaction",
          "next"
        ]
      },
      "Clip": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "e.g. `clip_42`."
          },
          "object": {
            "const": "clip"
          },
          "campaign": {
            "type": "string",
            "description": ""
          },
          "clipper": {
            "type": "string",
            "description": "Wallet paid for it."
          },
          "platform": {
            "type": "string",
            "description": "",
            "enum": [
              "tiktok",
              "instagram",
              "youtube",
              "x"
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "description": "`counting` is being paid; `held` looks bought and waits; `clawed_back` lost its unvested pay; `removed` was deleted; `rejected` failed the brief.",
            "enum": [
              "counting",
              "held",
              "clawed_back",
              "removed",
              "rejected"
            ]
          },
          "views": {
            "type": "integer",
            "description": "Latest view count."
          },
          "earned": {
            "type": "number",
            "description": "USDC earned so far."
          },
          "publishedAt": {
            "type": "string",
            "description": "",
            "format": "date-time"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why it is held or was rejected (funder and clipper only)."
          },
          "risk": {
            "type": [
              "string",
              "null"
            ],
            "description": "`low`, `medium` or `high` (funder and clipper only)."
          },
          "appeal": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/AppealSummary"
              },
              {
                "type": "null"
              }
            ],
            "description": "The clipper’s appeal against a clawback (funder and clipper only)."
          }
        },
        "required": [
          "id",
          "object",
          "campaign",
          "clipper",
          "platform",
          "url",
          "status",
          "views",
          "earned",
          "publishedAt"
        ]
      },
      "Submission": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "e.g. `sub_7`."
          },
          "object": {
            "const": "submission"
          },
          "campaign": {
            "type": "string",
            "description": ""
          },
          "clipper": {
            "type": "string",
            "description": ""
          },
          "url": {
            "type": "string",
            "description": "The post that was submitted."
          },
          "status": {
            "type": "string",
            "description": "`pending` is waiting for the post to show up on the clipper’s connected account (or for the check to run); `accepted` became a clip; `rejected` did not.",
            "enum": [
              "pending",
              "accepted",
              "rejected"
            ]
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "clip": {
            "type": [
              "string",
              "null"
            ],
            "description": "The clip it became."
          },
          "createdAt": {
            "type": "string",
            "description": "",
            "format": "date-time"
          },
          "checkedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "appeal": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/AppealSummary"
              },
              {
                "type": "null"
              }
            ],
            "description": "The clipper’s appeal, when it was rejected and they contested it."
          }
        },
        "required": [
          "id",
          "object",
          "campaign",
          "clipper",
          "url",
          "status",
          "reason",
          "clip",
          "createdAt",
          "checkedAt",
          "appeal"
        ]
      },
      "AppealSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "e.g. `apl_3`."
          },
          "note": {
            "type": "string",
            "description": "What the clipper wrote."
          },
          "status": {
            "type": "string",
            "description": "`open`; `reaccepted` once the funder re-accepted the submission.",
            "enum": [
              "open",
              "reaccepted"
            ]
          },
          "createdAt": {
            "type": "string",
            "description": "",
            "format": "date-time"
          },
          "resolvedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "note",
          "status",
          "createdAt",
          "resolvedAt"
        ]
      },
      "Appeal": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "e.g. `apl_3`."
          },
          "object": {
            "const": "appeal"
          },
          "campaign": {
            "type": "string",
            "description": ""
          },
          "clipper": {
            "type": "string",
            "description": ""
          },
          "submission": {
            "type": [
              "string",
              "null"
            ],
            "description": "The rejected submission it contests."
          },
          "clip": {
            "type": [
              "string",
              "null"
            ],
            "description": "The clawed-back clip it contests."
          },
          "note": {
            "type": "string",
            "description": "What the clipper wrote, up to 1,000 characters."
          },
          "status": {
            "type": "string",
            "description": "",
            "enum": [
              "open",
              "reaccepted"
            ]
          },
          "createdAt": {
            "type": "string",
            "description": "",
            "format": "date-time"
          },
          "resolvedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "object",
          "campaign",
          "clipper",
          "submission",
          "clip",
          "note",
          "status",
          "createdAt",
          "resolvedAt"
        ]
      },
      "Notification": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "e.g. `ntf_12`."
          },
          "object": {
            "const": "notification"
          },
          "type": {
            "type": "string",
            "description": "The event behind it.",
            "enum": [
              "submission.accepted",
              "submission.rejected",
              "clip.paid",
              "clip.vested",
              "clip.clawed_back",
              "campaign.ending_soon"
            ]
          },
          "title": {
            "type": "string",
            "description": "One line, for people."
          },
          "body": {
            "type": "string",
            "description": "A sentence or two, for people."
          },
          "campaign": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "description": "The page on the website it is about."
          },
          "data": {
            "type": "object",
            "description": "The event’s data, as in the webhook."
          },
          "createdAt": {
            "type": "string",
            "description": "",
            "format": "date-time"
          },
          "readAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "object",
          "type",
          "title",
          "body",
          "campaign",
          "url",
          "data",
          "createdAt",
          "readAt"
        ]
      },
      "Clipper": {
        "type": "object",
        "properties": {
          "object": {
            "const": "clipper"
          },
          "wallet": {
            "type": "string",
            "description": ""
          },
          "verified": {
            "type": "boolean",
            "description": "Verified as one person with Self (optional: one step up)."
          },
          "tier": {
            "type": "string",
            "description": "Trust tier. There is no earning limit; trust decides how long views wait before they are paid.",
            "enum": [
              "new",
              "trusted",
              "established"
            ]
          },
          "holdSeconds": {
            "type": "integer",
            "description": "How old this clipper’s views must be before they are paid (a clip judged medium risk waits at least a day; the hold shortens as a campaign’s withdrawal nears)."
          },
          "cleanWeeks": {
            "type": "integer",
            "description": "Weeks since the first paid clip, or since the last purged or held clip."
          },
          "recovering": {
            "type": "boolean",
            "description": "True for the weeks after a purged or held clip: back at `new`, and only a clean history moves the clipper up until then."
          },
          "nextTier": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "tier": {
                "type": "string",
                "description": "",
                "enum": [
                  "trusted",
                  "established"
                ]
              },
              "holdSeconds": {
                "type": "integer",
                "description": ""
              }
            },
            "required": [
              "tier",
              "holdSeconds"
            ],
            "description": "The next tier, or null at the top."
          },
          "moveUp": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "step": {
                  "type": "string",
                  "description": "",
                  "enum": [
                    "verify",
                    "bond",
                    "history"
                  ]
                },
                "description": {
                  "type": "string",
                  "description": ""
                },
                "amount": {
                  "type": "number",
                  "description": "`bond`: USDC to lock."
                },
                "cleanWeeks": {
                  "type": "integer",
                  "description": "`history`: clean weeks still needed."
                },
                "paid": {
                  "type": "number",
                  "description": "`history`: USDC still to be paid."
                }
              },
              "required": [
                "step",
                "description"
              ]
            },
            "description": "Each way to move one tier up; empty at the top."
          },
          "bond": {
            "type": "object",
            "properties": {
              "amount": {
                "type": "number",
                "description": ""
              },
              "locked": {
                "type": "boolean",
                "description": "Locked and at least the minimum: one tier up."
              },
              "unlocksAt": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            },
            "required": [
              "amount",
              "locked",
              "unlocksAt"
            ]
          },
          "earned": {
            "type": "number",
            "description": "Lifetime, after clawbacks."
          }
        },
        "required": [
          "object",
          "wallet",
          "verified",
          "tier",
          "holdSeconds",
          "cleanWeeks",
          "recovering",
          "nextTier",
          "moveUp",
          "bond",
          "earned"
        ]
      },
      "ApiKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "e.g. `key_1a2b3c4d5e6f7a8b`."
          },
          "object": {
            "const": "api_key"
          },
          "name": {
            "type": "string",
            "description": ""
          },
          "prefix": {
            "type": "string",
            "description": "The first characters, to recognise it."
          },
          "key": {
            "type": "string",
            "description": "The full key. Returned once, when it is created."
          },
          "wallet": {
            "type": "string",
            "description": ""
          },
          "createdAt": {
            "type": "string",
            "description": "",
            "format": "date-time"
          },
          "lastUsedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "object",
          "name",
          "prefix",
          "createdAt"
        ]
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "e.g. `wh_…`."
          },
          "object": {
            "const": "webhook"
          },
          "url": {
            "type": "string",
            "description": ""
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "description": ""
            },
            "description": "Subscribed event types, or `*`."
          },
          "secret": {
            "type": "string",
            "description": "`whsec_…`, for checking signatures. Returned once."
          },
          "createdAt": {
            "type": "string",
            "description": "",
            "format": "date-time"
          }
        },
        "required": [
          "id",
          "object",
          "url",
          "events",
          "createdAt"
        ]
      },
      "WebhookDelivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": ""
          },
          "object": {
            "const": "webhook_delivery"
          },
          "event": {
            "type": "string",
            "description": "Event id."
          },
          "type": {
            "type": "string",
            "description": ""
          },
          "status": {
            "type": "string",
            "description": "",
            "enum": [
              "pending",
              "delivered",
              "failed"
            ]
          },
          "attempts": {
            "type": "integer",
            "description": ""
          },
          "responseStatus": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "type": "string",
            "description": ""
          },
          "deliveredAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "nextAttemptAt": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "object",
          "event",
          "type",
          "status",
          "attempts",
          "responseStatus",
          "error",
          "createdAt",
          "deliveredAt",
          "nextAttemptAt"
        ]
      },
      "Event": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "`evt_…`; unique, use it to ignore repeats."
          },
          "type": {
            "type": "string",
            "description": "",
            "enum": [
              "campaign.activated",
              "campaign.ended",
              "submission.accepted",
              "submission.rejected",
              "clip.paid",
              "clip.vested",
              "clip.clawed_back",
              "campaign.ending_soon",
              "appeal.created",
              "ping"
            ]
          },
          "created": {
            "type": "string",
            "description": "",
            "format": "date-time"
          },
          "data": {
            "type": "object",
            "description": "Depends on the type; see the webhooks guide."
          }
        },
        "required": [
          "id",
          "type",
          "created",
          "data"
        ],
        "description": "The body of a webhook request."
      },
      "Config": {
        "type": "object",
        "properties": {
          "object": {
            "const": "config"
          },
          "network": {
            "type": "string",
            "description": "",
            "enum": [
              "mainnet",
              "devnet",
              "localnet"
            ]
          },
          "programId": {
            "type": "string",
            "description": "The payout program on Solana."
          },
          "usdcMint": {
            "type": "string",
            "description": ""
          },
          "feeBps": {
            "type": "integer",
            "description": "Fee on deposits, basis points."
          },
          "paidAfterSeconds": {
            "type": "object",
            "properties": {
              "lowRisk": {
                "type": "integer",
                "description": ""
              },
              "mediumRisk": {
                "type": "integer",
                "description": ""
              }
            },
            "required": [
              "lowRisk",
              "mediumRisk"
            ]
          },
          "instantShareBps": {
            "type": "integer",
            "description": "Share of each payout sent at once."
          },
          "vestingSeconds": {
            "type": "integer",
            "description": "The rest is released after this."
          },
          "withdrawGraceSeconds": {
            "type": "integer",
            "description": ""
          },
          "bondCooldownSeconds": {
            "type": "integer",
            "description": ""
          },
          "dailyCaps": {
            "type": "boolean",
            "description": "Whether campaigns can have a daily cap (`maxPerDay`); false on a network whose program predates them."
          },
          "paused": {
            "type": "boolean",
            "description": "True while the guardian has paused the oracle: no clips are registered and no views reported or paid until it is lifted. Vested payments, withdrawals, top-ups and bonds keep working."
          },
          "trust": {
            "type": "object",
            "properties": {
              "holdSeconds": {
                "type": "object",
                "properties": {
                  "new": {
                    "type": "integer",
                    "description": "A new wallet."
                  },
                  "trusted": {
                    "type": "integer",
                    "description": ""
                  },
                  "established": {
                    "type": "integer",
                    "description": ""
                  }
                },
                "required": [
                  "new",
                  "trusted",
                  "established"
                ]
              },
              "trustedAfterCleanWeeks": {
                "type": "integer",
                "description": ""
              },
              "establishedAfterCleanWeeks": {
                "type": "integer",
                "description": ""
              },
              "establishedAfterPaid": {
                "type": "number",
                "description": "USDC paid, after clawbacks."
              },
              "bondMinimum": {
                "type": "number",
                "description": "A locked bond of at least this moves a clipper one tier up; so does Self verification."
              },
              "endMarginSeconds": {
                "type": "integer",
                "description": "The trust hold ends this long before a campaign’s withdrawal, so late views are still paid."
              }
            },
            "required": [
              "holdSeconds",
              "trustedAfterCleanWeeks",
              "establishedAfterCleanWeeks",
              "establishedAfterPaid",
              "bondMinimum",
              "endMarginSeconds"
            ],
            "description": "Views are paid once old enough for the clipper’s trust tier and the clip’s risk (the longer of the two). No earning limit per clipper: the limits are the funder’s."
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "description": ""
            },
            "description": "Webhook event types."
          },
          "terms": {
            "type": "object",
            "properties": {
              "version": {
                "type": "string",
                "description": "The version a signed challenge accepts."
              },
              "url": {
                "type": "string",
                "description": "The terms."
              },
              "privacyUrl": {
                "type": "string",
                "description": "The privacy notice."
              }
            },
            "required": [
              "version",
              "url",
              "privacyUrl"
            ]
          }
        },
        "required": [
          "object",
          "network",
          "programId",
          "usdcMint",
          "feeBps",
          "paidAfterSeconds",
          "instantShareBps",
          "vestingSeconds",
          "withdrawGraceSeconds",
          "bondCooldownSeconds",
          "dailyCaps",
          "paused",
          "trust",
          "events",
          "terms"
        ]
      }
    }
  },
  "paths": {
    "/v1/campaigns": {
      "get": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "listCampaigns",
        "summary": "List campaigns",
        "description": "Public. Live campaigns by default, newest first.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "live",
                "ended",
                "all"
              ],
              "default": "live"
            }
          },
          {
            "name": "sort",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "newest",
                "rate",
                "budget",
                "ending"
              ],
              "default": "newest"
            },
            "description": "`rate`: highest pay first; `budget`: most left first; `ending`: soonest end first."
          },
          {
            "name": "funder",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Only campaigns funded by this wallet."
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Items per page, 1 to 100.",
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The `nextCursor` of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of campaigns.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Campaign"
                      },
                      "description": "This page of results."
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "Whether there is a next page."
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass as `cursor` for the next page."
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "hasMore",
                    "nextCursor"
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "id": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                      "object": "campaign",
                      "url": "https://clips.example/c/J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                      "status": "live",
                      "title": "Acme",
                      "description": "Clip the best Acme moments from the stream. Funny, fast, and on-brand.",
                      "imageUrl": null,
                      "logoUrl": null,
                      "funder": "4KV2gW9mWqhWJrYRe9LqBgwMsUWD4tYaRcxDbvyjLpES",
                      "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                      "ratePer1k": 1,
                      "maxPerClip": 50,
                      "maxPerDay": 40,
                      "budget": {
                        "funded": 475,
                        "paid": 12.4,
                        "remaining": 456.9,
                        "vesting": 5.7
                      },
                      "viewsRemaining": 456900,
                      "startAt": "2026-10-17T12:00:00.000Z",
                      "endAt": "2026-11-16T12:00:00.000Z",
                      "withdrawAfter": "2026-11-19T12:00:00.000Z",
                      "platforms": [
                        "tiktok",
                        "instagram",
                        "youtube"
                      ],
                      "audience": {
                        "countries": [
                          "US",
                          "GB",
                          "CA",
                          "AU",
                          "NZ"
                        ],
                        "minShare": 0.5,
                        "allowUnknown": false,
                        "minFollowers": 0
                      },
                      "brief": {
                        "content": [
                          {
                            "label": "Stream",
                            "url": "https://www.twitch.tv/videos/2901852985"
                          }
                        ],
                        "clipRequirements": [
                          "Mention Acme in some capacity in the on-screen caption"
                        ],
                        "captionRequirements": [],
                        "violations": [
                          "Logos or watermarks of other brands",
                          "Not using the provided content",
                          "Bought views, bots or engagement groups"
                        ]
                      },
                      "assets": [],
                      "clips": 14,
                      "isFunder": false
                    }
                  ],
                  "hasMore": false,
                  "nextCursor": null
                }
              }
            }
          },
          "400": {
            "description": "sort is newest, rate, budget or ending",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "code": "parameter_invalid",
                    "message": "sort is newest, rate, budget or ending",
                    "param": "sort",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "createCampaign",
        "summary": "Create a campaign",
        "description": "Validates the brief and returns the Solana transaction that creates and funds the campaign. Sign it with your wallet, then call **activate** with the signed transaction. Until then nothing is on chain and nothing is public. Supports `Idempotency-Key`.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "schema": {
              "type": "string"
            },
            "description": "Any unique string; a retry with the same key returns the first response."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignInput"
              },
              "example": {
                "title": "Acme",
                "description": "Clip the best Acme moments from the stream. Funny, fast, and on-brand.",
                "content": [
                  {
                    "label": "Stream",
                    "url": "https://www.twitch.tv/videos/2901852985"
                  }
                ],
                "clipRequirements": [
                  "Mention Acme in some capacity in the on-screen caption"
                ],
                "captionRequirements": [],
                "platforms": [
                  "tiktok",
                  "instagram",
                  "youtube"
                ],
                "audienceCountries": [
                  "US",
                  "GB",
                  "CA",
                  "AU",
                  "NZ"
                ],
                "minAudienceShare": 0.5,
                "budget": 500,
                "ratePer1k": 1,
                "durationDays": 30
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The draft and the transaction to sign.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignDraft"
                },
                "example": {
                  "object": "campaign_draft",
                  "campaign": {
                    "id": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                    "object": "campaign",
                    "url": "https://clips.example/c/J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                    "status": "awaiting_signature",
                    "title": "Acme",
                    "funder": "4KV2gW9mWqhWJrYRe9LqBgwMsUWD4tYaRcxDbvyjLpES",
                    "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                    "ratePer1k": 1,
                    "maxPerClip": 50,
                    "maxPerDay": null,
                    "deposit": 500,
                    "endAt": "2026-11-16T12:00:00.000Z"
                  },
                  "fee": 25,
                  "transaction": {
                    "encoding": "base64",
                    "transaction": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAkU…",
                    "lastValidBlockHeight": 310442917,
                    "signer": "4KV2gW9mWqhWJrYRe9LqBgwMsUWD4tYaRcxDbvyjLpES"
                  },
                  "next": "POST /v1/campaigns/J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR/activate with { \"signedTransaction\": \"…\" }"
                }
              }
            }
          },
          "400": {
            "description": "add the content to clip: at least one https link or uploaded video",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "code": "parameter_invalid",
                    "message": "add the content to clip: at least one https link or uploaded video",
                    "param": "content",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          },
          "401": {
            "description": "this endpoint needs an API key: Authorization: Bearer ck_…",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "authentication_error",
                    "code": "authentication_required",
                    "message": "this endpoint needs an API key: Authorization: Bearer ck_…",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/campaigns/{id}": {
      "get": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "getCampaign",
        "summary": "Get a campaign",
        "description": "Public. Budget figures are read from the chain.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The campaign’s address on Solana.",
            "schema": {
              "type": "string"
            },
            "example": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR"
          }
        ],
        "responses": {
          "200": {
            "description": "The campaign.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Campaign"
                },
                "example": {
                  "id": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                  "object": "campaign",
                  "url": "https://clips.example/c/J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                  "status": "live",
                  "title": "Acme",
                  "description": "Clip the best Acme moments from the stream. Funny, fast, and on-brand.",
                  "imageUrl": null,
                  "logoUrl": null,
                  "funder": "4KV2gW9mWqhWJrYRe9LqBgwMsUWD4tYaRcxDbvyjLpES",
                  "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                  "ratePer1k": 1,
                  "maxPerClip": 50,
                  "maxPerDay": 40,
                  "budget": {
                    "funded": 475,
                    "paid": 12.4,
                    "remaining": 456.9,
                    "vesting": 5.7
                  },
                  "viewsRemaining": 456900,
                  "startAt": "2026-10-17T12:00:00.000Z",
                  "endAt": "2026-11-16T12:00:00.000Z",
                  "withdrawAfter": "2026-11-19T12:00:00.000Z",
                  "platforms": [
                    "tiktok",
                    "instagram",
                    "youtube"
                  ],
                  "audience": {
                    "countries": [
                      "US",
                      "GB",
                      "CA",
                      "AU",
                      "NZ"
                    ],
                    "minShare": 0.5,
                    "allowUnknown": false,
                    "minFollowers": 0
                  },
                  "brief": {
                    "content": [
                      {
                        "label": "Stream",
                        "url": "https://www.twitch.tv/videos/2901852985"
                      }
                    ],
                    "clipRequirements": [
                      "Mention Acme in some capacity in the on-screen caption"
                    ],
                    "captionRequirements": [],
                    "violations": [
                      "Logos or watermarks of other brands",
                      "Not using the provided content",
                      "Bought views, bots or engagement groups"
                    ]
                  },
                  "assets": [],
                  "clips": 14,
                  "isFunder": false
                }
              }
            }
          },
          "404": {
            "description": "no campaign J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "not_found",
                    "code": "campaign_not_found",
                    "message": "no campaign J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "updateCampaign",
        "summary": "Update a campaign",
        "description": "Funder only. Edits the brief and the texts; money, rate and dates are on chain and fixed. New requirements apply to clips checked from now on.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The campaign’s address on Solana.",
            "schema": {
              "type": "string"
            },
            "example": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignPatch"
              },
              "example": {
                "description": "Now with the new flavour: clip the taste test at 1:02:10.",
                "clipRequirements": [
                  "Mention Acme in the on-screen caption",
                  "Show the can at least once"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated campaign.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Campaign"
                },
                "example": {
                  "id": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                  "object": "campaign",
                  "url": "https://clips.example/c/J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                  "status": "live",
                  "title": "Acme",
                  "description": "Clip the best Acme moments from the stream. Funny, fast, and on-brand.",
                  "imageUrl": null,
                  "logoUrl": null,
                  "funder": "4KV2gW9mWqhWJrYRe9LqBgwMsUWD4tYaRcxDbvyjLpES",
                  "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                  "ratePer1k": 1,
                  "maxPerClip": 50,
                  "maxPerDay": 40,
                  "budget": {
                    "funded": 475,
                    "paid": 12.4,
                    "remaining": 456.9,
                    "vesting": 5.7
                  },
                  "viewsRemaining": 456900,
                  "startAt": "2026-10-17T12:00:00.000Z",
                  "endAt": "2026-11-16T12:00:00.000Z",
                  "withdrawAfter": "2026-11-19T12:00:00.000Z",
                  "platforms": [
                    "tiktok",
                    "instagram",
                    "youtube"
                  ],
                  "audience": {
                    "countries": [
                      "US",
                      "GB",
                      "CA",
                      "AU",
                      "NZ"
                    ],
                    "minShare": 0.5,
                    "allowUnknown": false,
                    "minFollowers": 0
                  },
                  "brief": {
                    "content": [
                      {
                        "label": "Stream",
                        "url": "https://www.twitch.tv/videos/2901852985"
                      }
                    ],
                    "clipRequirements": [
                      "Mention Acme in some capacity in the on-screen caption"
                    ],
                    "captionRequirements": [],
                    "violations": [
                      "Logos or watermarks of other brands",
                      "Not using the provided content",
                      "Bought views, bots or engagement groups"
                    ]
                  },
                  "assets": [],
                  "clips": 14,
                  "isFunder": false
                }
              }
            }
          },
          "403": {
            "description": "only the wallet that funds this campaign can do this",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "permission_error",
                    "code": "not_funder",
                    "message": "only the wallet that funds this campaign can do this",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/campaigns/{id}/activate": {
      "post": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "activateCampaign",
        "summary": "Activate a campaign",
        "description": "Sends the signed creation transaction (or, without `signedTransaction`, checks that you submitted it yourself), then publishes the brief. Safe to retry: an active campaign is returned as it is.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The campaign’s address on Solana.",
            "schema": {
              "type": "string"
            },
            "example": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "signedTransaction": {
                    "type": "string",
                    "description": "The transaction from **create**, signed by your wallet, base64. Omit if you submitted it yourself."
                  }
                },
                "required": []
              },
              "example": {
                "signedTransaction": "AQHx8F0kq…"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The live campaign.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Campaign"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "transactionSignature": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": []
                    }
                  ]
                },
                "example": {
                  "id": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                  "object": "campaign",
                  "url": "https://clips.example/c/J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                  "status": "live",
                  "title": "Acme",
                  "description": "Clip the best Acme moments from the stream. Funny, fast, and on-brand.",
                  "imageUrl": null,
                  "logoUrl": null,
                  "funder": "4KV2gW9mWqhWJrYRe9LqBgwMsUWD4tYaRcxDbvyjLpES",
                  "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
                  "ratePer1k": 1,
                  "maxPerClip": 50,
                  "maxPerDay": 40,
                  "budget": {
                    "funded": 475,
                    "paid": 12.4,
                    "remaining": 456.9,
                    "vesting": 5.7
                  },
                  "viewsRemaining": 456900,
                  "startAt": "2026-10-17T12:00:00.000Z",
                  "endAt": "2026-11-16T12:00:00.000Z",
                  "withdrawAfter": "2026-11-19T12:00:00.000Z",
                  "platforms": [
                    "tiktok",
                    "instagram",
                    "youtube"
                  ],
                  "audience": {
                    "countries": [
                      "US",
                      "GB",
                      "CA",
                      "AU",
                      "NZ"
                    ],
                    "minShare": 0.5,
                    "allowUnknown": false,
                    "minFollowers": 0
                  },
                  "brief": {
                    "content": [
                      {
                        "label": "Stream",
                        "url": "https://www.twitch.tv/videos/2901852985"
                      }
                    ],
                    "clipRequirements": [
                      "Mention Acme in some capacity in the on-screen caption"
                    ],
                    "captionRequirements": [],
                    "violations": [
                      "Logos or watermarks of other brands",
                      "Not using the provided content",
                      "Bought views, bots or engagement groups"
                    ]
                  },
                  "assets": [],
                  "clips": 0,
                  "isFunder": false,
                  "transactionSignature": "5i1AJnXGv6q4w8k9qnggM3nsVTiYAUmyWNprriTGi1GJGJ6BEzt1im2TPxktRNgzADcFtPo89R6fVepS5qjeKA2e"
                }
              }
            }
          },
          "400": {
            "description": "the transaction failed: blockhash not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "code": "transaction_failed",
                    "message": "the transaction failed: blockhash not found",
                    "param": "signedTransaction",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          },
          "409": {
            "description": "the campaign is not on chain yet: send the signed transaction (signedTransaction), or submit it yourself and retry",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "conflict",
                    "code": "campaign_not_on_chain",
                    "message": "the campaign is not on chain yet: send the signed transaction (signedTransaction), or submit it yourself and retry",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/campaigns/{id}/fund": {
      "post": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "fundCampaign",
        "summary": "Top up a campaign",
        "description": "Anyone can add to a live campaign’s budget. Returns the transaction for the caller’s wallet to sign; relay it with POST /v1/transactions.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The campaign’s address on Solana.",
            "schema": {
              "type": "string"
            },
            "example": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount": {
                    "type": "number",
                    "description": "USDC, the fee is taken from it.",
                    "minimum": 1
                  }
                },
                "required": [
                  "amount"
                ]
              },
              "example": {
                "amount": 250
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The transaction to sign.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransactionRequest"
                },
                "example": {
                  "object": "transaction_request",
                  "action": "fund",
                  "campaign": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                  "amount": 250,
                  "fee": 12.5,
                  "transaction": {
                    "encoding": "base64",
                    "transaction": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAkU…",
                    "lastValidBlockHeight": 310442917,
                    "signer": "4KV2gW9mWqhWJrYRe9LqBgwMsUWD4tYaRcxDbvyjLpES"
                  }
                }
              }
            }
          },
          "409": {
            "description": "this campaign has ended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "conflict",
                    "code": "campaign_ended",
                    "message": "this campaign has ended",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/campaigns/{id}/stop": {
      "post": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "stopCampaign",
        "summary": "Stop a campaign early",
        "description": "Funder only. No new clips from now; clips already in are still paid until `withdrawAfter`, for the views they had when it stopped.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The campaign’s address on Solana.",
            "schema": {
              "type": "string"
            },
            "example": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR"
          }
        ],
        "responses": {
          "200": {
            "description": "The transaction to sign.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransactionRequest"
                },
                "example": {
                  "object": "transaction_request",
                  "action": "stop",
                  "campaign": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                  "transaction": {
                    "encoding": "base64",
                    "transaction": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAkU…",
                    "lastValidBlockHeight": 310442917,
                    "signer": "4KV2gW9mWqhWJrYRe9LqBgwMsUWD4tYaRcxDbvyjLpES"
                  }
                }
              }
            }
          },
          "403": {
            "description": "only the wallet that funds this campaign can do this",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "permission_error",
                    "code": "not_funder",
                    "message": "only the wallet that funds this campaign can do this",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/campaigns/{id}/withdraw": {
      "post": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "withdrawCampaign",
        "summary": "Take back the rest",
        "description": "Funder only, from `withdrawAfter`. Money still vesting to clippers stays with them; while nothing else is left, there is nothing to withdraw (`nothing_to_withdraw`). The campaign closes with the withdrawal made after the last vesting payment has been released.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The campaign’s address on Solana.",
            "schema": {
              "type": "string"
            },
            "example": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR"
          }
        ],
        "responses": {
          "200": {
            "description": "The transaction to sign.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransactionRequest"
                },
                "example": {
                  "object": "transaction_request",
                  "action": "withdraw",
                  "campaign": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                  "amount": 182.5,
                  "transaction": {
                    "encoding": "base64",
                    "transaction": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAkU…",
                    "lastValidBlockHeight": 310442917,
                    "signer": "4KV2gW9mWqhWJrYRe9LqBgwMsUWD4tYaRcxDbvyjLpES"
                  }
                }
              }
            }
          },
          "409": {
            "description": "`too_early`: the rest can be withdrawn from 2026-11-19T12:00:00.000Z, after clips posted in time are paid; `nothing_to_withdraw`: nothing is left to withdraw now: 12.5 USDC is still vesting to clippers; withdraw once it has been released to close the campaign; `campaign_closed`: this campaign is closed; nothing is left to withdraw",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "too_early": {
                    "value": {
                      "error": {
                        "type": "conflict",
                        "code": "too_early",
                        "message": "the rest can be withdrawn from 2026-11-19T12:00:00.000Z, after clips posted in time are paid",
                        "requestId": "req_3f9a1c2e7b4d5a60"
                      }
                    }
                  },
                  "nothing_to_withdraw": {
                    "value": {
                      "error": {
                        "type": "conflict",
                        "code": "nothing_to_withdraw",
                        "message": "nothing is left to withdraw now: 12.5 USDC is still vesting to clippers; withdraw once it has been released to close the campaign",
                        "requestId": "req_3f9a1c2e7b4d5a60"
                      }
                    }
                  },
                  "campaign_closed": {
                    "value": {
                      "error": {
                        "type": "conflict",
                        "code": "campaign_closed",
                        "message": "this campaign is closed; nothing is left to withdraw",
                        "requestId": "req_3f9a1c2e7b4d5a60"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/campaigns/{id}/cap": {
      "post": {
        "tags": [
          "Campaigns"
        ],
        "operationId": "setCampaignCap",
        "summary": "Set the daily cap",
        "description": "Funder only. The most the campaign pays out per UTC day, raised, lowered or removed at any time until it closes; it takes effect with the next report. A report past the cap pays what the cap allows and the rest of those views stay owed, paid on the following days. Sign the returned transaction and relay it with POST /v1/transactions.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The campaign’s address on Solana.",
            "schema": {
              "type": "string"
            },
            "example": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "maxPerDay": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "USDC per day, at least 0.01; null or 0 removes the cap."
                  }
                },
                "required": [
                  "maxPerDay"
                ]
              },
              "example": {
                "maxPerDay": 40
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The transaction to sign.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransactionRequest"
                },
                "example": {
                  "object": "transaction_request",
                  "action": "set_cap",
                  "campaign": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                  "maxPerDay": 40,
                  "transaction": {
                    "encoding": "base64",
                    "transaction": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAkU…",
                    "lastValidBlockHeight": 310442917,
                    "signer": "4KV2gW9mWqhWJrYRe9LqBgwMsUWD4tYaRcxDbvyjLpES"
                  }
                }
              }
            }
          },
          "400": {
            "description": "maxPerDay must be at least 0.01",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "code": "parameter_invalid",
                    "message": "maxPerDay must be at least 0.01",
                    "param": "maxPerDay",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          },
          "403": {
            "description": "only the wallet that funds this campaign can do this",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "permission_error",
                    "code": "not_funder",
                    "message": "only the wallet that funds this campaign can do this",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          },
          "409": {
            "description": "`campaign_closed`: this campaign is closed; `caps_unavailable`: daily caps are not available on devnet yet",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "campaign_closed": {
                    "value": {
                      "error": {
                        "type": "conflict",
                        "code": "campaign_closed",
                        "message": "this campaign is closed",
                        "requestId": "req_3f9a1c2e7b4d5a60"
                      }
                    }
                  },
                  "caps_unavailable": {
                    "value": {
                      "error": {
                        "type": "conflict",
                        "code": "caps_unavailable",
                        "message": "daily caps are not available on devnet yet",
                        "requestId": "req_3f9a1c2e7b4d5a60"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/campaigns/{id}/clips": {
      "get": {
        "tags": [
          "Clips"
        ],
        "operationId": "listCampaignClips",
        "summary": "List a campaign’s clips",
        "description": "Public: clips counting, most viewed first. The funder also sees held, rejected and clawed-back clips with reasons.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The campaign’s address on Solana.",
            "schema": {
              "type": "string"
            },
            "example": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "counting",
                "held",
                "clawed_back",
                "removed",
                "rejected"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Items per page, 1 to 100.",
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The `nextCursor` of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of clips.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Clip"
                      },
                      "description": "This page of results."
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "Whether there is a next page."
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass as `cursor` for the next page."
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "hasMore",
                    "nextCursor"
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "id": "clip_42",
                      "object": "clip",
                      "campaign": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                      "clipper": "9o4Zszv2Qb6yXJ5o2H7sV2kq9H9c4d8u3P6xg1Yb2ZcR",
                      "platform": "tiktok",
                      "url": "https://www.tiktok.com/@clipper/video/7412345678901234567",
                      "status": "counting",
                      "views": 48210,
                      "earned": 48.21,
                      "publishedAt": "2026-10-18T09:12:00.000Z"
                    }
                  ],
                  "hasMore": false,
                  "nextCursor": null
                }
              }
            }
          }
        }
      }
    },
    "/v1/campaigns/{id}/submissions": {
      "get": {
        "tags": [
          "Clips"
        ],
        "operationId": "listCampaignSubmissions",
        "summary": "List a campaign’s submissions",
        "description": "Funder only: every submitted link, what became of it, and the clipper’s appeal when there is one.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The campaign’s address on Solana.",
            "schema": {
              "type": "string"
            },
            "example": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "accepted",
                "rejected"
              ]
            }
          },
          {
            "name": "appealed",
            "in": "query",
            "description": "`true`: only appealed ones; `false`: only the others.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Items per page, 1 to 100.",
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The `nextCursor` of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of submissions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Submission"
                      },
                      "description": "This page of results."
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "Whether there is a next page."
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass as `cursor` for the next page."
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "hasMore",
                    "nextCursor"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/submissions/{submissionId}/reaccept": {
      "post": {
        "tags": [
          "Clips"
        ],
        "operationId": "reacceptSubmission",
        "summary": "Re-accept a rejected submission",
        "description": "Funder only, while the campaign is live. The submission goes through the checks again (the clipper’s connected account, the dates, followers, platforms and audience) and, if it passes, becomes a clip paid by its views like any other. Your re-acceptance replaces the brief check, which judges the brief on your behalf. There is no way to pay a clip by hand. Answers like POST /v1/submissions: `200` when decided, `202` while the post has not shown up yet. Any open appeal on it becomes `reaccepted`.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "submissionId",
            "in": "path",
            "required": true,
            "description": "e.g. `sub_7`.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Checked again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Submission"
                }
              }
            }
          },
          "202": {
            "description": "Waiting for the post to show up on the clipper’s account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Submission"
                }
              }
            }
          },
          "403": {
            "description": "only the wallet that funds this campaign can do this",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "permission_error",
                    "code": "not_funder",
                    "message": "only the wallet that funds this campaign can do this",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          },
          "404": {
            "description": "no submission sub_7",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "not_found",
                    "code": "submission_not_found",
                    "message": "no submission sub_7",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          },
          "409": {
            "description": "only a rejected submission can be re-accepted; this one is accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "conflict",
                    "code": "not_rejected",
                    "message": "only a rejected submission can be re-accepted; this one is accepted",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/appeals": {
      "post": {
        "tags": [
          "Clips"
        ],
        "operationId": "createAppeal",
        "summary": "Appeal a rejection or a clawback",
        "description": "A clipper contests their rejected submission or clawed-back clip with one note (once per submission or clip). The funder sees it on the campaign page, in its submissions and clips, and gets an `appeal.created` event; they can re-accept a rejected submission. A clawback stays: clips are never paid by hand.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "submission": {
                    "type": "string",
                    "description": "`sub_…`, or"
                  },
                  "clip": {
                    "type": "string",
                    "description": "`clip_…` (one of the two)."
                  },
                  "note": {
                    "type": "string",
                    "description": "Why it should count, up to 1,000 characters."
                  }
                },
                "required": [
                  "note"
                ]
              },
              "example": {
                "submission": "sub_7",
                "note": "The logo is on screen from 0:02 to the end; the check missed it."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Stored and sent to the funder.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Appeal"
                },
                "example": {
                  "id": "apl_3",
                  "object": "appeal",
                  "campaign": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                  "clipper": "4KV2gW9mWqhWJrYRe9LqBgwMsUWD4tYaRcxDbvyjLpES",
                  "submission": "sub_7",
                  "clip": null,
                  "note": "The logo is on screen from 0:02 to the end; the check missed it.",
                  "status": "open",
                  "createdAt": "2026-10-19T10:00:00.000Z",
                  "resolvedAt": null
                }
              }
            }
          },
          "400": {
            "description": "note is required: say why it should count",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "code": "parameter_missing",
                    "message": "note is required: say why it should count",
                    "param": "note",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          },
          "404": {
            "description": "no submission sub_7 of yours",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "not_found",
                    "code": "submission_not_found",
                    "message": "no submission sub_7 of yours",
                    "param": "submission",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          },
          "409": {
            "description": "this was appealed already; an appeal is one note",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "conflict",
                    "code": "already_appealed",
                    "message": "this was appealed already; an appeal is one note",
                    "param": "submission",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/submissions": {
      "post": {
        "tags": [
          "Clips"
        ],
        "operationId": "submitClip",
        "summary": "Submit a clip",
        "description": "A clipper submits a post for a campaign. It must be on an account they connected (on the website) and meet the brief. A post on a platform the campaign does not take is refused with `400 platform_not_allowed`. When the campaign targets countries, the account’s audience must be there (see `audience`); an account whose platform has not reported its audience yet stays `pending` for up to two days. Answers `200` with the outcome when it is decided at once, `202` with `status: pending` when the post has not shown up on the account yet (it is checked again for two days).",
        "security": [
          {
            "apiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "campaign": {
                    "type": "string",
                    "description": "Campaign id."
                  },
                  "url": {
                    "type": "string",
                    "description": "The post: TikTok, Instagram, YouTube or X."
                  }
                },
                "required": [
                  "campaign",
                  "url"
                ]
              },
              "example": {
                "campaign": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                "url": "https://www.tiktok.com/@clipper/video/7412345678901234567"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Decided.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Submission"
                },
                "example": {
                  "id": "sub_7",
                  "object": "submission",
                  "campaign": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                  "clipper": "9o4Zszv2Qb6yXJ5o2H7sV2kq9H9c4d8u3P6xg1Yb2ZcR",
                  "url": "https://www.tiktok.com/@clipper/video/7412345678901234567",
                  "status": "accepted",
                  "reason": null,
                  "clip": "clip_42",
                  "createdAt": "2026-10-18T09:20:00.000Z",
                  "checkedAt": "2026-10-18T09:20:04.000Z"
                }
              }
            }
          },
          "202": {
            "description": "Waiting for the post to show up on the clipper’s account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Submission"
                }
              }
            }
          },
          "400": {
            "description": "url must be a TikTok, Instagram, YouTube or X post",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "code": "parameter_invalid",
                    "message": "url must be a TikTok, Instagram, YouTube or X post",
                    "param": "url",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          },
          "409": {
            "description": "this campaign has ended",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "conflict",
                    "code": "campaign_ended",
                    "message": "this campaign has ended",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "tags": [
          "Clips"
        ],
        "operationId": "getMe",
        "summary": "Your clipper account",
        "description": "The caller’s trust tier, how long their views are held before they are paid, what moves them up, bond and earnings.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "responses": {
          "200": {
            "description": "The clipper.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Clipper"
                },
                "example": {
                  "object": "clipper",
                  "wallet": "4KV2gW9mWqhWJrYRe9LqBgwMsUWD4tYaRcxDbvyjLpES",
                  "verified": false,
                  "tier": "trusted",
                  "holdSeconds": 86400,
                  "cleanWeeks": 1,
                  "recovering": false,
                  "nextTier": {
                    "tier": "established",
                    "holdSeconds": 3600
                  },
                  "moveUp": [
                    {
                      "step": "verify",
                      "description": "Verify with Self that you are one person (optional, your passport or ID stays on your phone)"
                    },
                    {
                      "step": "history",
                      "cleanWeeks": 3,
                      "paid": 0,
                      "description": "3 more clean weeks"
                    }
                  ],
                  "bond": {
                    "amount": 25,
                    "locked": true,
                    "unlocksAt": null
                  },
                  "earned": 96.2
                }
              }
            }
          }
        }
      }
    },
    "/v1/me/clips": {
      "get": {
        "tags": [
          "Clips"
        ],
        "operationId": "listMyClips",
        "summary": "Your clips",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Items per page, 1 to 100.",
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The `nextCursor` of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of clips.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Clip"
                      },
                      "description": "This page of results."
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "Whether there is a next page."
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass as `cursor` for the next page."
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "hasMore",
                    "nextCursor"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/me/submissions": {
      "get": {
        "tags": [
          "Clips"
        ],
        "operationId": "listMySubmissions",
        "summary": "Your submissions",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Items per page, 1 to 100.",
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The `nextCursor` of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of submissions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Submission"
                      },
                      "description": "This page of results."
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "Whether there is a next page."
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass as `cursor` for the next page."
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "hasMore",
                    "nextCursor"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/me/notifications": {
      "get": {
        "tags": [
          "Clips"
        ],
        "operationId": "listMyNotifications",
        "summary": "Your notifications",
        "description": "The clipper’s feed, newest first (the last 200): submissions accepted or rejected with the reason, clips paid, vested or clawed back, campaigns you clip for ending within a day. `unread` counts the unread ones.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "unread",
            "in": "query",
            "description": "`true`: unread only.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Items per page, 1 to 100.",
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The `nextCursor` of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of notifications.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "type": "object",
                      "properties": {
                        "object": {
                          "const": "list"
                        },
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Notification"
                          },
                          "description": "This page of results."
                        },
                        "hasMore": {
                          "type": "boolean",
                          "description": "Whether there is a next page."
                        },
                        "nextCursor": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Pass as `cursor` for the next page."
                        }
                      },
                      "required": [
                        "object",
                        "data",
                        "hasMore",
                        "nextCursor"
                      ]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "unread": {
                          "type": "integer",
                          "description": "How many are unread."
                        }
                      },
                      "required": [
                        "unread"
                      ]
                    }
                  ]
                },
                "example": {
                  "object": "list",
                  "data": [
                    {
                      "id": "ntf_12",
                      "object": "notification",
                      "type": "submission.rejected",
                      "title": "Clip not accepted for Acme",
                      "body": "Reason: posted before the campaign started. If you think this is wrong, you can appeal once from your dashboard.",
                      "campaign": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR",
                      "url": "https://clipz.family/me",
                      "data": {},
                      "createdAt": "2026-10-19T09:00:00.000Z",
                      "readAt": null
                    }
                  ],
                  "hasMore": false,
                  "nextCursor": null,
                  "unread": 1
                }
              }
            }
          }
        }
      }
    },
    "/v1/me/notifications/read": {
      "post": {
        "tags": [
          "Clips"
        ],
        "operationId": "markNotificationsRead",
        "summary": "Mark notifications read",
        "description": "These ids, or all of them when `ids` is left out.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "description": ""
                    },
                    "description": "`ntf_…` ids; omit for all."
                  }
                },
                "required": []
              },
              "example": {
                "ids": [
                  "ntf_12"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Done.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "notifications_read"
                    },
                    "updated": {
                      "type": "integer",
                      "description": "How many changed."
                    },
                    "unread": {
                      "type": "integer",
                      "description": "How many are still unread."
                    }
                  },
                  "required": [
                    "object",
                    "updated",
                    "unread"
                  ]
                },
                "example": {
                  "object": "notifications_read",
                  "updated": 1,
                  "unread": 0
                }
              }
            }
          }
        }
      }
    },
    "/v1/transactions": {
      "post": {
        "tags": [
          "Transactions"
        ],
        "operationId": "relayTransaction",
        "summary": "Send a signed transaction",
        "description": "Sends a transaction this API built (fund, stop, withdraw) after your wallet signed it, and waits for confirmation. Only clips transactions are relayed. You can also submit them through any RPC yourself.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "signedTransaction": {
                    "type": "string",
                    "description": "Base64."
                  },
                  "campaign": {
                    "type": "string",
                    "description": "Optional: refreshes that campaign’s cached figures."
                  }
                },
                "required": [
                  "signedTransaction"
                ]
              },
              "example": {
                "signedTransaction": "AQHx8F0kq…",
                "campaign": "J7xu1BVoTgymFb1zHw2ckq3iwFQyTmRZSnf8XbXPr9pR"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransactionResult"
                },
                "example": {
                  "object": "transaction",
                  "signature": "5i1AJnXGv6q4w8k9qnggM3nsVTiYAUmyWNprriTGi1GJGJ6BEzt1im2TPxktRNgzADcFtPo89R6fVepS5qjeKA2e",
                  "explorer": "https://explorer.solana.com/tx/5i1AJ…"
                }
              }
            }
          },
          "400": {
            "description": "`unsigned_transaction`: the transaction is not fully signed; `foreign_transaction`: only clips transactions are relayed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "examples": {
                  "unsigned_transaction": {
                    "value": {
                      "error": {
                        "type": "invalid_request_error",
                        "code": "unsigned_transaction",
                        "message": "the transaction is not fully signed",
                        "param": "signedTransaction",
                        "requestId": "req_3f9a1c2e7b4d5a60"
                      }
                    }
                  },
                  "foreign_transaction": {
                    "value": {
                      "error": {
                        "type": "invalid_request_error",
                        "code": "foreign_transaction",
                        "message": "only clips transactions are relayed",
                        "param": "signedTransaction",
                        "requestId": "req_3f9a1c2e7b4d5a60"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/uploads": {
      "post": {
        "tags": [
          "Uploads"
        ],
        "operationId": "upload",
        "summary": "Upload a video or logo",
        "description": "Send the file as the raw body with its Content-Type (video/mp4, video/quicktime, video/webm, image/png, image/jpeg, image/webp), up to 500 MB. Use the returned id in `videos` or `logo` when creating a campaign.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "video/mp4": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            },
            "image/png": {
              "schema": {
                "type": "string",
                "format": "binary"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Stored.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Upload"
                },
                "example": {
                  "id": "3f9a1c2e7b4d5a60b1c2.mp4",
                  "object": "upload",
                  "contentType": "video/mp4",
                  "url": "https://clipz.family/api/media/3f9a1c2e7b4d5a60b1c2.mp4"
                }
              }
            }
          },
          "415": {
            "description": "upload an MP4, MOV, WebM, PNG, JPEG or WebP file with its Content-Type",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "code": "unsupported_media_type",
                    "message": "upload an MP4, MOV, WebM, PNG, JPEG or WebP file with its Content-Type",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "createWebhook",
        "summary": "Create a webhook",
        "description": "Events about your campaigns (as funder) and your clips (as clipper) are POSTed to `url`, signed with the returned `secret`. Up to 10 per wallet.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "https endpoint."
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "description": ""
                    },
                    "description": "Event types; omit for all."
                  }
                },
                "required": [
                  "url"
                ]
              },
              "example": {
                "url": "https://example.com/clips/webhook",
                "events": [
                  "submission.accepted",
                  "clip.paid",
                  "campaign.ended"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created. Store the secret; it is shown once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                },
                "example": {
                  "id": "wh_5a1f0c2d9e8b7a6c",
                  "object": "webhook",
                  "url": "https://example.com/clips/webhook",
                  "events": [
                    "submission.accepted",
                    "clip.paid",
                    "campaign.ended"
                  ],
                  "secret": "whsec_Pn3cJ2m9v1oQ8R4sTzY0aKb6Wd5XeF7h",
                  "createdAt": "2026-10-18T09:00:00.000Z"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhooks",
        "summary": "List webhooks",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Items per page, 1 to 100.",
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The `nextCursor` of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Your webhooks (without secrets).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Webhook"
                      },
                      "description": "This page of results."
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "Whether there is a next page."
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass as `cursor` for the next page."
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "hasMore",
                    "nextCursor"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{webhookId}": {
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "deleteWebhook",
        "summary": "Delete a webhook",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "webhookId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{webhookId}/test": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "testWebhook",
        "summary": "Send a test event",
        "description": "Delivers a `ping` event now and reports how your endpoint answered.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "webhookId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The outcome.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookTest"
                },
                "example": {
                  "object": "webhook_test",
                  "event": "evt_9a8b7c6d5e4f3a2b1c0d9e8f",
                  "delivered": true,
                  "responseStatus": 200,
                  "error": null
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{webhookId}/deliveries": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhookDeliveries",
        "summary": "List deliveries",
        "description": "The last 200 attempts, newest first.",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "webhookId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Items per page, 1 to 100.",
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The `nextCursor` of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of deliveries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookDelivery"
                      },
                      "description": "This page of results."
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "Whether there is a next page."
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass as `cursor` for the next page."
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "hasMore",
                    "nextCursor"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/auth/challenge": {
      "post": {
        "tags": [
          "Keys"
        ],
        "operationId": "createChallenge",
        "summary": "Get a message to sign",
        "description": "Step one of creating a key without the website: sign the returned message with the wallet (ed25519, as `signMessage` does), then call POST /v1/keys. Valid for 10 minutes, once. Signing it accepts the terms and privacy notice it names (`terms` in GET /v1/config).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "wallet": {
                    "type": "string",
                    "description": "Your wallet address."
                  }
                },
                "required": [
                  "wallet"
                ]
              },
              "example": {
                "wallet": "4KV2gW9mWqhWJrYRe9LqBgwMsUWD4tYaRcxDbvyjLpES"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Challenge"
                },
                "example": {
                  "object": "challenge",
                  "wallet": "4KV2gW9mWqhWJrYRe9LqBgwMsUWD4tYaRcxDbvyjLpES",
                  "message": "clips wants you to sign in with your Solana account:\n4KV2gW9mWqhWJrYRe9LqBgwMsUWD4tYaRcxDbvyjLpES\n\nCreate an API key for clips. This costs nothing and sends no transaction.\n\nBy signing you accept the clips Terms (https://clipz.family/terms) and Privacy Notice (https://clipz.family/privacy), version 2026-10-20 (draft).\n\nURI: https://clipz.family\nNonce: 6c1f0e9a2b3d4c5e6f708192a3b4c5d6\nIssued At: 2026-10-18T09:00:00.000Z",
                  "expiresAt": "2026-10-18T09:10:00.000Z"
                }
              }
            }
          }
        }
      }
    },
    "/v1/keys": {
      "post": {
        "tags": [
          "Keys"
        ],
        "operationId": "createKey",
        "summary": "Create an API key",
        "description": "With a signed challenge (`wallet`, `message`, `signature` in base58), or authenticated with an existing key. The key is returned once; we only keep its hash.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "To recognise it later."
                  },
                  "wallet": {
                    "type": "string",
                    "description": ""
                  },
                  "message": {
                    "type": "string",
                    "description": "From POST /v1/auth/challenge."
                  },
                  "signature": {
                    "type": "string",
                    "description": "base58 ed25519 signature of `message`."
                  }
                },
                "required": []
              },
              "example": {
                "name": "Production server",
                "wallet": "4KV2gW9mWqhWJrYRe9LqBgwMsUWD4tYaRcxDbvyjLpES",
                "message": "clips wants you to sign in with your Solana account:\n…",
                "signature": "3yZe7d…"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKey"
                },
                "example": {
                  "id": "key_1a2b3c4d5e6f7a8b",
                  "object": "api_key",
                  "name": "Production server",
                  "wallet": "4KV2gW9mWqhWJrYRe9LqBgwMsUWD4tYaRcxDbvyjLpES",
                  "key": "ck_live_9fK2mQ7xL4pV8nB1cR6tY3wZ5hJ0dS2a",
                  "prefix": "ck_live_9fK2",
                  "createdAt": "2026-10-18T09:00:05.000Z"
                }
              }
            }
          },
          "400": {
            "description": "the signature does not match the wallet and message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "type": "invalid_request_error",
                    "code": "invalid_signature",
                    "message": "the signature does not match the wallet and message",
                    "param": "signature",
                    "requestId": "req_3f9a1c2e7b4d5a60"
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Keys"
        ],
        "operationId": "listKeys",
        "summary": "List API keys",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Items per page, 1 to 100.",
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "The `nextCursor` of the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Your keys (prefixes only).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "object": {
                      "const": "list"
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiKey"
                      },
                      "description": "This page of results."
                    },
                    "hasMore": {
                      "type": "boolean",
                      "description": "Whether there is a next page."
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass as `cursor` for the next page."
                    }
                  },
                  "required": [
                    "object",
                    "data",
                    "hasMore",
                    "nextCursor"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/v1/keys/{keyId}": {
      "delete": {
        "tags": [
          "Keys"
        ],
        "operationId": "revokeKey",
        "summary": "Revoke an API key",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "keyId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked; it stops working at once.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          }
        }
      }
    },
    "/v1/config": {
      "get": {
        "tags": [
          "Platform"
        ],
        "operationId": "getConfig",
        "summary": "Settings",
        "description": "Public: network, program, USDC mint, fee, payout timing, trust holds, webhook events.",
        "responses": {
          "200": {
            "description": "The settings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Config"
                }
              }
            }
          }
        }
      }
    },
    "/v1/stats": {
      "get": {
        "tags": [
          "Platform"
        ],
        "operationId": "getStats",
        "summary": "Totals",
        "description": "Public: paid to clippers, live campaigns, clips, clippers, views.",
        "responses": {
          "200": {
            "description": "Totals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Stats"
                },
                "example": {
                  "object": "stats",
                  "paid": 18423.5,
                  "liveCampaigns": 12,
                  "clips": 840,
                  "clippers": 213,
                  "views": 18423500
                }
              }
            }
          }
        }
      }
    }
  }
}