REST API & MCP server
UGC Pocket exposes an ordering layer for agents: estimate then order a UGC campaign over REST or MCP, and, on the creator side, declare published videos. An agent order always creates a campaign in draft status; a human confirms and funds it in the app. No payment is ever triggered by an agent.
Overview
| REST base | https://ugcpocket.com/api |
| MCP server | https://ugcpocket.com/mcp · Streamable HTTP, protocol 2025-06-18 |
| Specification | /openapi.json (OpenAPI 3.1) |
| Currency | EUR, amounts in cents |
| Campaign budget | 300,000 to 10,000,000 cents (€2,000 to €100,000) |
The draft created by an agent belongs to the organization that owns the key. The response includes a confirm_url that opens the campaign in the app for confirmation and funding. budget_max_cents is the campaign package, invoiced in full: UGC Pocket briefs, selects and pays the creators, and commits to getting the most views it can.
Getting started
An agent cannot sign up on its own: access is bootstrapped by a human in the app.
- Download the UGC Pocket app, then create an organization account (brand side) or a creator account. An agent cannot sign up on its own.
- Generate a key: brands in Profile → "API/MCP keys", creators in Settings → "API/MCP keys". The
ugcp_live_…key is shown only once. - Configure the agent with the
Authorization: Bearer ugcp_live_…header.
First request
curl -X POST https://ugcpocket.com/api/campaigns \
-H "Authorization: Bearer ugcp_live_..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"title": "Kibble launch",
"brief": "TikTok video featuring the product…",
"prestation": "video_post",
"target_categories": ["dog"],
"platforms": ["TikTok"],
"budget_max_cents": 350000
}'
HTTP/1.1 201 Created { "campaign_id": "8b1f…", "status": "draft", "ordered_by_agent": true, "next_step": "The brand must confirm and fund the campaign in the UGC Pocket app.", "confirm_url": "https://ugcpocket.com/c/8b1f…" }
Authentication
API keys use the ugcp_live_… format, sent in the Authorization: Bearer header. A key carries a scope that determines which surfaces it can reach.
| Scope | Generated by | Grants access to |
|---|---|---|
| campaigns:draft | Organization (brand) account | POST /campaigns, GET /campaigns/{id}, and the 3 brand MCP tools. |
| creator:submit | Creator account | The 3 creator MCP tools: accepted campaigns, posted-video submission, deliverables. |
A key with the wrong scope gets insufficient_scope (403) over REST, and a business rejection over MCP. Keys are revoked in the app, in the same place they were created; a revoked key then answers key_revoked (401), an expired one key_expired (401).
GET /service-info, POST /estimate), as is MCP discovery (initialize, tools/list) and the get_service_info tool. Everything else requires a key. Mind the asymmetry: POST /estimate is public over REST, but the MCP tool estimate_campaign requires a campaigns:draft key.REST API
Base https://ugcpocket.com/api. Responses are JSON; errors use the envelope { "error": "code", "detail": "…" }. Key-authenticated routes emit no CORS headers: they are built for server-to-server calls, not for a browser.
| Endpoint | Auth | Purpose |
|---|---|---|
GET/service-info | none | Product descriptor: enums, budget floor, links. |
POST/estimate | none | Free-text brief → suggested fields + estimate. |
POST/campaigns | campaigns:draft | Creates a campaign draft. |
GET/campaigns/{id} | API key | Status of a campaign in the organization. |
Service descriptor, no authentication. Returns the valid enum values (categories, prestations, platforms), currency, budget_unit, min_campaign_budget_cents, the ordering model, and links to openapi.json, mcp and these docs. Call it first: it is the source of truth for enums. GET / returns the same payload.
Analyzes a free-text brief (French or English) and returns structured fields plus an estimate of what the cap can produce. Writes nothing to the database.
// Request { "brief": "UGC videos for our new kibble, dog owners, max budget €5,000" } // 200 OK { "suggested": { "title": "…", "prestation": "video_post", "target_categories": ["dog"], "platforms": ["TikTok"], "budget_max_cents": 500000 }, "estimate": { "budget_max_cents": 500000, "estimated_creators": 33, "estimated_videos": 165, "max_views": 3750000, "note": "…" } }
estimate is null when no budget can be inferred from the brief. A missing or non-string brief returns validation_error (422).
Creates a campaign in draft status. Body: a CampaignDraftInput object. Requires a campaigns:draft key. Returns 201 with campaign_id, status, ordered_by_agent, next_step, confirm_url.
Optional Idempotency-Key header: the response is cached for 24 h per key, so replaying the same request returns the same campaign_id instead of creating a duplicate. Use one UUID per ordering intent, not per attempt.
Returns campaign_id, title, status, ordered_by_agent and confirm_url. The lookup is scoped to the key owner's organization: an id belonging to another organization returns not_found (404), not 403.
Possible statuses: draft, open, in_progress, completed, cancelled.
CampaignDraftInput
The object sent to POST /campaigns and to the MCP tool create_campaign_order. Full schema in /openapi.json.
The input is coerced, not strictly rejected. Only two things return an error: a missing required field, and budget_max_cents below the floor. Everything else is silently normalised, so read the response rather than assuming your payload went through as written: title and brief are truncated to their maximum length, unknown enum values are dropped from target_categories and platforms, an invalid prestation falls back to video_post, objectives keeps its first 6 entries, a budget above the ceiling is clamped to 10000000, and undeclared fields are ignored.
| Field | Type | Details |
|---|---|---|
titlerequired | string ≤ 120 | Short campaign title. |
briefrequired | string ≤ 2000 | Creative guidelines, tone, constraints. |
budget_max_centsrequired | integer | Euro cents. Minimum 200000 (€2,000), below that a 422. Above 10000000 (€100,000) the value is clamped, not refused. The campaign package, invoiced in full. |
prestation | enum | video_post (default and only value). |
target_categories | enum[] | See the 22 categories below. |
platforms | enum[] | TikTok, Instagram, YouTube, Snapchat, Facebook. |
objectives | string[] | Free-form campaign objectives. |
brief_raw | string | The original untouched brief, stored as-is. |
payment_trigger | enum | publication or views. |
views_threshold | integer ≥ 0 | Views threshold, when payment_trigger=views. |
deadline | ISO date | YYYY-MM-DD. |
Categories
dogcatpetscarcookingcouplefamilykidsbusinessfinancepodcastsportwellnessbeautyfashiontechgaminghomegardendiytravellifestyle
These are the values the API accepts. Call GET /service-info rather than hard-coding them.
Errors
Every REST error uses the { "error", "detail" } envelope. The error field is stable; detail is a human-readable message that may change.
| HTTP | error | Cause |
|---|---|---|
401 | invalid_api_key | Missing, unknown or malformed key. |
401 | key_revoked | Key revoked in the app. |
401 | key_expired | Key past its expiry. |
403 | insufficient_scope | The key does not carry campaigns:draft. |
404 | not_found | Campaign missing, outside the organization, or unknown route. |
422 | validation_error | Missing title, non-string brief, or budget_max_cents absent, non-numeric or below 200000. Unknown enums and undeclared fields do not land here: they are dropped silently. |
429 | rate_limited | Rate exceeded. Retry-After: 60 header. |
400 | create_failed | Write rejected by the database: 200-draft cap over 24 h reached, or the key is not authorized for the organization. The reason is in detail. |
500 | internal | Server error. Safe to replay with the same Idempotency-Key. |
Limits
| Limit | Value | Behaviour when exceeded |
|---|---|---|
| Rate per key | 30 requests / minute | 429 rate_limited + Retry-After: 60 |
| Drafts per key | 200 per rolling 24 h window | 400 create_failed, detail = "daily draft cap reached" |
| Campaign budget | 300,000 – 10,000,000 cents | Below the floor: 422 validation_error. Above the ceiling: clamped to 10,000,000. |
| Idempotency cache | 24 h per key | After that, the same idempotency key creates a new campaign |
MCP server
Streamable HTTP MCP server at https://ugcpocket.com/mcp, protocol 2025-06-18, ugc-pocket-mcp v1.2.0. JSON transport only: the server emits no SSE stream, and a client that does not accept application/json gets 406. Only POST is accepted.
Discovery (initialize, tools/list) and the get_service_info tool work without a key. The other six tools require the key header.
Client configuration
{
"mcpServers": {
"ugc-pocket": {
"type": "http",
"url": "https://ugcpocket.com/mcp",
"headers": { "Authorization": "Bearer ugcp_live_YOUR_KEY" }
}
}
}
Brand tools campaigns:draft
| Tool | Input | Purpose |
|---|---|---|
get_service_infono key | none | Product descriptor and valid enums. Read-only, idempotent. |
estimate_campaign | brief required | Free-text brief → structured fields + estimate (creators, videos, maximum views). Writes nothing. |
create_campaign_order | CampaignDraftInput | Creates a draft. Accepts the HTTP Idempotency-Key header, as REST does. |
check_campaign_status | campaign_id required | Status of a campaign in the organization. |
Creator tools creator:submit
Built for creators declaring many published videos, for example several hundred during a challenge. The key is generated on the creator side in Settings → "API/MCP keys".
| Tool | Input | Purpose |
|---|---|---|
list_my_campaigns | none | Campaigns you are selected on: campaign_id, title, status, package, payment trigger, remuneration, deadline, brief. |
submit_posted_video | campaign_id, video_url required | Declares a published video. TikTok, Instagram or YouTube URLs get daily view tracking for 1 month; other https URLs are stored without tracking. Idempotent per campaign + URL pair, with already_submitted flagging a replay. 30 requests/min. |
list_my_deliverables | campaign_id, before (optional) | Your videos and their state (submitted, published, validated, revision, paid), tracked views, validated amounts in cents. 200 per call; paginate by passing the oldest created_at as before. |
JSON-RPC errors
Two distinct registers, which an agent should not conflate: a protocol failure is an error object, a business rejection is a result with isError: true and an actionable message.
| Code | Cause |
|---|---|
-32001 | Missing or invalid key. HTTP 401 + WWW-Authenticate: Bearer. |
-32601 | Unknown tool or method. |
-32602 | Missing required argument. |
-32700 / -32600 | Unparseable JSON / invalid JSON-RPC request. |
-32603 | Internal error. |
isError: true | Business rejection: wrong scope, budget out of bounds, campaign not found, daily cap. |
Machine resources
| Resource | Purpose |
|---|---|
/openapi.json | OpenAPI 3.1 specification of the REST API. |
/llms.txt | LLM-readable product discovery and context. |
/llms-full.txt | Extended version. |
Integration questions: hello@logics-studio.com