Skip to content

Endpoint reference

Participant endpoints live under the /api/v1/public/ prefix on https://api.vibevo.io and requires an API key with the right scope. The two guest creator-catalog GETs under /api/v1/ need no API key and are described below.

Creator catalog and placements

Guest reads are separate from protected participant operations. Paths below are relative to https://api.vibevo.io/api/v1.

Guest catalog

GET /creator-catalog accepts q, channelType (STREAMER or SOCIAL), platform, format, topic, language, geo, audienceGeo, minPriceCents, maxPriceCents, availability, sort (price, relevance, activity), limit (1–100), and cursor. audienceGeo is an ISO country code from a positive share in the latest current verified audience distribution; lowercase input is normalized to uppercase. The legacy geo filter remains a creator profile tag. platform and audienceGeo must match the same active published service channel. Price sorting and its cursor use the creator’s minimum published service price, which can differ from the service selected by the filters. The only accepted availability value is AVAILABLE; the filter uses published services. Order dates and “by agreement” are not availability filters.

curl 'https://api.vibevo.io/api/v1/creator-catalog?channelType=STREAMER&availability=AVAILABLE&limit=20'

# Anonymous response (public fields only)
{
  "data": [{
    "publicId": "d8e805e4-1ec5-4ab6-a734-4ea389cecc6c",
    "metricsObservationId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "lastModified": "2026-10-08T12:00:00.000Z",
    "displayName": "Streamer One",
    "topics": ["gaming"],
    "languages": ["en"],
    "channelType": "STREAMER",
    "channels": [{"platform":"TWITCH","handle":"streamer_one","canonicalUrl":"https://www.twitch.tv/streamer_one"}],
    "services": [{
      "serviceListingId": 7, "type": "STREAM", "variant": "MENTION",
      "channel":{"platform":"TWITCH","handle":"streamer_one","canonicalUrl":"https://www.twitch.tv/streamer_one"},
      "proofModes":["PUBLIC_VOD"],
      "title": "Product mention", "unit": "broadcast",
      "basePrice": {"raw":"100000000","currency":"USDT","decimals":6},
      "packageOptions": [{"quantity":1,"discountBps":0,"packageBase":{"raw":"100000000","currency":"USDT","decimals":6}},{"quantity":5,"discountBps":500,"packageBase":{"raw":"475000000","currency":"USDT","decimals":6}}]
    }],
    "metrics": []
  }],
  "nextCursor": null
}

GET /creator-pages/{publicId} returns a page while the creator has published it and it has available services with usage terms. Otherwise the API responds 404 with code RESOURCE_NOT_FOUND. Pre-publication review of the profile, prices, services, or manual URL is not required. Drafts, contacts, proof files, schedules, and fee fields are omitted. Unpublishing revokes page availability, HTML, Open Graph, cache, and sitemap entries; unpublished pages are not indexed.

Public response and prices

The catalog shows the published service base price, without fees or creator earnings. A money value is {raw, currency: "USDT", decimals: 6}; raw is an integer number of USDT micro-units. For example, raw "100000000" means 100 USDT. Requests send priceCents as an integer cents string; a USDT price input has at most two decimal places.

For a connected verified social account, channels[].audience may contain verified geography, age and gender for that exact account: source, capturedAt, verifiedAt, periodDays, geo and demo. Average views use known non-negative view counts from the latest five public posts. Manual channels have no trusted audience metrics; missing data stays unavailable, never zero. Private snapshot/evidence/connection IDs are omitted.

Protected participant flow

Every path in this list has the /public prefix and requires an API key: GET requests need read; mutations need write plus current role and object permissions.

  • Creator: GET/PATCH /public/influencer-profile; GET /public/influencer-profile/page; POST /public/influencer-profile/page/generate, /submit, /page/publish, and /page/unpublish; GET/POST /public/influencer-channels; GET/PUT /public/influencer-channels/{id}/pricing; POST /public/influencer-channels/{id}/ownership-challenge, /ownership-uploads, /ownership-uploads/{uploadId}/complete, /ownership-evidence, and /submit. Profile, page, channel, ownership, and pricing routes also have JWT twins under /api/v1 without /public. GET/PUT /public/influencer-accounts/{accountId}/pricing remains a compatibility alias for the owner’s active, verified social account; channel pricing is canonical. The compatible publishAfterApproval field is boolean: true publishes the current version immediately without pre-publication review; false only generates a private preview. Manual channels accept a safe public HTTPS URL for any permitted platform; legacy handle-only input remains supported for Twitch/Kick. Ownership verification is optional and does not block publication or purchase. Native accounts must remain active, verified, owned by the creator, unblocked, unsuspended, unsold, and undeleted. The JWT-only POST /api/v1/creator/social-accounts/{id}/avatar/refresh refreshes an owner’s account avatar without a new OAuth flow and accepts no source URL in the body. Owner DTOs may include optional socialAccountId and avatarUrl. No MCP avatar tool is added.
  • Advertiser: POST /public/placement-orders creates a draft; PATCH /public/placement-orders/{id} changes it; POST /public/placement-orders/{id}/quote prices it; POST /public/placement-orders/{id}/checkout confirms funding. Custom terms use POST /public/placement-offers/quote and POST /public/placement-offers. Read orders and analytics with GET /public/placement-orders, GET /public/placement-orders/{id}, GET /public/analytics/placements, and GET /public/placement-orders/{id}/analytics.
  • After funding, order parties use GET /public/placement-assignments, GET /public/placement-assignments/{id}, /events, and /deliverables; actions include accept, decline, submit, approve, request-changes, cancel, issues, and change-proposals (propose/accept a new due date). Update a result with PUT /public/placement-assignments/{id}/deliverables/{deliverableId}; private uploads support create/complete and attached-result download. JWT twins use the same routes under /api/v1 without /public. Staff use separate JWT-only review routes.

If publication is temporarily disabled, an explicit publish request saves its intent and returns 503 WRITE_NOT_AVAILABLE_FOR_FEATURE. After the release control is enabled, retry the same request with the same UUID Idempotency-Key. This does not create staff review; existing published pages remain available and unpublish still works.

Create an order

curl -X POST 'https://api.vibevo.io/api/v1/public/placement-orders' \
  -H "Authorization: Bearer $VIBEVO_API_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: 7ddc2979-9a7f-4d4f-8f89-38ea3353e444' \
  -d '{"items":[{"serviceListingId":7,"quantity":1,"discountBps":0,"dueAt":"2026-10-20T18:00:00.000Z","brief":"Mention the product during the broadcast.","results":[{"expectedAt":"2026-10-20T17:00:00.000Z"}],"rights":{}}]}'

results must contain exactly as many obligations as selected placements (1, 5, or 10); expectedAt is each publication date and dueAt is the overall deadline. rights carries the agreed usage terms.

Quote and confirmation

POST /api/v1/public/placement-orders/{id}/quote
Idempotency-Key: <stable UUID>
{ "expectedVersion": 1 }

# Advertiser-safe quote projection
{
  "data": {
    "quoteId": "9e955328-7a2b-42a4-9963-330fcc83ad92",
    "quoteVersion": 1, "orderId": 14,
    "expiresAt": "2026-10-08T12:10:00.000Z",
    "payloadHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "currency": "USDT",
    "items": [{"serviceListingId":7,"base":{"raw":"100000000","currency":"USDT","decimals":6},"brandFee":{"raw":"5000000","currency":"USDT","decimals":6},"total":{"raw":"105000000","currency":"USDT","decimals":6}}],
    "totals": {"base":{"raw":"100000000","currency":"USDT","decimals":6},"brandFee":{"raw":"5000000","currency":"USDT","decimals":6},"total":{"raw":"105000000","currency":"USDT","decimals":6}}
  }
}

POST /api/v1/public/placement-orders/{id}/checkout
Idempotency-Key: <new stable UUID>
{ "quoteId": "9e955328-7a2b-42a4-9963-330fcc83ad92", "quoteVersion": 1, "payloadHash": "<copy the quote hash>", "confirm": true }

A quote returns the creator base amount, the brand’s fee, and the total reserve to the advertiser only. The client displays these amounts and explicitly confirms checkout with quoteId, quoteVersion, payloadHash, and confirm:true. The creator’s fee and net earnings are omitted from advertiser DTOs. The creator’s own pricing response contains the base amount, creator fee, and net earnings; it omits the brand fee.

Private UGC custom-offer example

POST /api/v1/public/placement-offers/quote
Idempotency-Key: <stable UUID>
{
  "origin": "CUSTOM",
  "creatorPublicId": "d8e805e4-1ec5-4ab6-a734-4ea389cecc6c",
  "serviceType": "UGC",
  "proofModes": ["PRIVATE_FILE"],
  "priceCents": "12000",
  "expectedEndAt": "2026-10-20T18:00:00.000Z",
  "quantity": 1,
  "discountBps": 0,
  "brief": "Create and deliver a short private product video.",
  "results": [{"expectedAt":"2026-10-20T17:00:00.000Z","description":"Deliver the private video"}],
  "rights": {"usage":"As agreed in the brief"}
}

# Quote response: { data: { quoteId, quoteVersion, payloadHash, items: [{ serviceListingId: null, base, brandFee, total }], totals, ... } }

POST /api/v1/public/placement-offers
Idempotency-Key: <new stable UUID>
{ "quoteId": "<quote ID>", "quoteVersion": 1, "payloadHash": "<quote hash>", "confirm": true }

# Without serviceListingId, only UGC + PRIVATE_FILE is accepted; do not send accountId or channelRef.

When serviceListingId is omitted, only serviceType "UGC" with proofModes ["PRIVATE_FILE"] is accepted. Do not send accountId or channelRef. The quote shows the base amount, brand fee, and total reserve and has serviceListingId null. A FILE result has null channelRef and publishedRevision; url and contentId are null and metrics is empty. It has no public URL or social metrics.

Retries and errors

A UUID Idempotency-Key is required for profile PATCH, page/generate, channel create, pricing PUT, and the order, assignment, and ownership mutations that specify it in their contracts. For an unknown result, retry the same method, path, UUID, and body; reusing a key with another body/action/target returns IDEMPOTENCY_CONFLICT. Fresh role and ownership checks run before replay. Profile PATCH returns its saved DTO, while page/generate rebuilds the preview. Pricing PUT returns the saved operation/version with a fresh current projection; replay does not repeat a money operation or price change.

Private uploads
Create an owner-scoped or assignment-scoped upload session, PUT the file directly to its short-lived signed URL, then call complete. Do not send full files/base64 through the API or MCP; do not send the API key to the storage URL. A file is not evidence until the server verifies it and marks it VERIFIED. Initial limits: images up to 10 MiB, videos up to 100 MiB; PDF up to 50 MiB only for templates that allow it. URLs, tokens, and proof files never enter the catalog, Open Graph, logs, or public cache.

Structured errors

Error responses include a stable code and safe message. Possible codes include API_KEY_INVALID (401), API_KEY_MISSING_SCOPE / ROLE_NOT_ALLOWED / ACCOUNT_ACCESS_DENIED (403), RESOURCE_NOT_FOUND (404), INVALID_MONEY_PRECISION / INVALID_PROOF (400), VERSION_CONFLICT / QUOTE_EXPIRED / PRICE_CHANGED / IDEMPOTENCY_CONFLICT / INSUFFICIENT_FUNDS (409), RATE_LIMITED (429), and SERVICE_UNAVAILABLE / PRIVATE_UPLOAD_SERVICE_UNAVAILABLE (503). A foreign or inaccessible object appears as not found.

Example of a safe error body:

{"code":"IDEMPOTENCY_CONFLICT","message":"The idempotency key was used with a different payload."}

Key identity

  • GET /me — the current key's identity (owner, scopes). Any valid key works.

Campaigns

  • GET /campaigns — list campaigns. Scope read. Cursor pagination.
  • GET /campaigns/{id} — one campaign. Scope read.
  • GET /campaigns/{id}/posts — campaign posts. Scope read. Cursor + optional status filter.
  • GET /campaigns/{id}/creators — creators aggregated by account, ranked by views. Scope read.
  • GET /campaigns/{id}/tracking-links — tracking links with uniqueClicks. Scope read.

ER and saves

ER = 100 × (likes + comments) / views, rounded to two decimals. It uses the latest complete observations of views, likes and comments. Aggregates use summed counters rather than an average of post percentages. ER is null with zero views or no complete observation. Saves and reposts are excluded from the formula.

Campaign lists, details, analytics, posts and creators return engagement with scope: accepted_video. Only accepted video posts are included; a post row covers that post and a creator row covers that account. Use engagement.views as the denominator: the existing totalViews may include other statuses or represent paid views.

Factories expose canonical ER in metrics.er (totals.er in a summary) and saves in metrics.saves. History points and snapshotBaseline contain the same fields. The existing top-level er retains its earlier semantics; new clients use the nested field. Missing new fields in an older response do not prove a measurement.

saves is a separate last-known counter. Automatic sources are TikTok collectCount and X bookmarkCount, only when the provider returns a valid value. Other platforms have no value without an actual measurement. 0 means measured zero; null means unavailable. Do not replace null with zero. Legacy campaign history does not prove raw views: new ER becomes available after a normal scan, without a mass paid rescan.

erCoverage and savesCoverage contain measuredPosts / totalPosts for the entire selected cohort before pagination. Partial coverage means a partial aggregate. updatedAt and savesUpdatedAt are the oldest included measurement times; null means unknown freshness. Carrying an old counter into a new snapshot does not refresh its time. ER and saves chart values are absolute snapshots, without a growth mode.

Report hiddenFields accepts saves: the server removes its count, coverage and measurement time. Hiding views, likes, comments or er also removes dependent ER; hiding posts removes coverage, and lastScanned removes measurement times. These rules apply to summaries, rows, history and baselines.

{
  "engagement": {
    "scope": "accepted_video",
    "views": 100,
    "likes": 0,
    "comments": 0,
    "er": 0,
    "saves": null,
    "erCoverage": { "measuredPosts": 1, "totalPosts": 2 },
    "savesCoverage": { "measuredPosts": 0, "totalPosts": 2 },
    "updatedAt": "2026-10-09T08:00:00.000Z",
    "savesUpdatedAt": null
  }
}

Offers (for creators)

  • GET /offers — the creator's offer wall (RUNNING campaigns they can work on) with creator-facing terms (reward/CPM, requirements, takenAt), without the advertiser's budget/spend. Scope read. Cursor paginated.
  • GET /posts — your own submitted posts across all campaigns: status, views, earnings, campaign, account and submission date, plus a summary (total, on-moderation, total earned). Scope read. Cursor + optional status / platform filters.

Social accounts (for creators)

  • GET /social-accounts — your own social accounts: platform, handle, status, isVerified, verifiedVia (PLATFORM_API / BIO_CODE / MANUAL), statsAvailability and the socialConnection grant, when there is one. Scope read.
  • GET /social-accounts/{id}/verification — the active bio-verification code and what to do with it. Scope read.
  • GET /social-accounts/{id}/stats — account statistics: the current screen-recording session, the audience snapshot and the history. Scope read.

Content factories

Manage factory accounts, tags, monitoring and public reports through the API.

  • GET /factories/tags — read
  • GET /factories/{id}/tag-groups — read
  • GET /factories/credits — read
  • GET /factories/{id}/credits — read
  • GET /factories/{id}/credit-usage — read
  • GET /factories/{id}/credit-history — read
  • GET /factories/{id}/share-links — read

Intervals: 24, 12, 10, 8, 6, 4 hours from the last scan; tracking: 1–365 days. REST uses scansPerPostPerDay = 24 / intervalHours. History includes credits/debits only; usage covers all time, 30 days and today UTC.

Omit id in the usage/history tools to read /factories/credits/usage and /factories/credits/history for the shared wallet, including purchases.

hiddenFields redacts fields server-side, including derived values and links. All fields are visible by default. Other owners’ factories and staff reports remain inaccessible. Save sharePath at creation; the secret is not returned again.

Analytics

  • GET /analytics/campaigns/{id} — campaign analytics. Scope read.

Wallet

  • GET /wallet — current balance. Scope read.
  • GET /wallet/transactions — billing history. Scope read. Cursor pagination.

Cursor pagination

List endpoints use cursor pagination. Pass limit (1 to 100) and cursor (the id of the last item from the previous page):

GET /api/v1/public/campaigns?limit=50&cursor=1234

The response contains a data array and the next-page cursor:

{
  "data": [ /* ... */ ],
  "nextCursor": "5678"
}

When nextCursor is null, there are no more pages.

Money format

Monetary values are returned as a struct:

{
  "raw": "1500",
  "currency": "USD",
  "decimals": 2
}

raw is an integer in minor units, sent as a string; the real value = raw / 10^decimals. Note the two different scales:

  • Campaign budgets and post earnings — cents: decimals: 2, currency USD (e.g. raw: "1500" = 15.00 USD).
  • Wallet balance and billing transactions — USDT minor units: decimals: 6.

Error format

Errors come back in a single envelope:

{
  "statusCode": 403,
  "code": "insufficient_scope",
  "message": "Missing required scope: read",
  "timestamp": "2026-07-11T12:00:00.000Z",
  "path": "/api/v1/public/campaigns"
}

Branch on the string code field for programmatic handling — it is more stable than the human-readable message.

curl examples

List campaigns (Bearer header):

curl https://api.vibevo.io/api/v1/public/campaigns?limit=20 \
  -H "Authorization: Bearer vibevo_live_ab12cd.xxxxxxxxxxxxxxxx"

Wallet balance (X-API-Key header):

curl https://api.vibevo.io/api/v1/public/wallet \
  -H "X-API-Key: vibevo_live_ab12cd.xxxxxxxxxxxxxxxx"

Was this article helpful?

Updated: October 9, 2026

VibeVO API endpoint reference | VibeVO Docs