Skip to content

Developer docs

Marketing API

Products, strategy, metrics, calendar, contents, approvals, reports and the AI marketer — every screen of the app as REST endpoints.

The marketing API follows the same flow as the app: product → strategy → calendar → contents → approvals → reports. All routes are under /v1, accept an API key or an OAuth token, and are scoped by workspaceId (id or slug) in the body / query or by the resource id. Lists return either an array or { items, nextCursor } (noted as paged).

Products

EndpointBody / query → result
GET /v1/products?workspaceId=&archived=1ProductDto[]
POST /v1/products{ workspaceId, name, kind?, url?, storeUrls?, summary?, description?, goals?, monthlyBudgetKrw?, strategyTemplateId?, channelKinds? } → ProductDetailDto. strategyTemplateId applies the template right away
GET /v1/products/{id}ProductDetailDto — brand kit, audiences, latest strategy doc per section, funnel, channels
PATCH /v1/products/{id}Partial update → ProductDetailDto
DELETE /v1/products/{id}Archive → { ok }
POST /v1/products/scrape{ url } → ProductScrapeResult — Open Graph, App Store / Play listing, GitHub repo + README; null fields on failure
PUT /v1/products/{id}/brand-kit{ tone?, forbidden?, colors?, logoAssetId?, examples?, fonts? } → BrandKitDto
POST /v1/products/{id}/images · DELETE /v1/products/{id}/images/{assetId}Product gallery (ProductDetailDto.images, max 24) — { assetId } of an image you uploaded via POST /v1/uploads/presign. Set the icon with PATCH … { iconAssetId }, the logo with brand-kit logoAssetId
GET / POST /v1/products/{id}/audiences · PATCH / DELETE /v1/products/{id}/audiences/{aid}Target audiences
GET / POST /v1/notes?workspaceId=&productId= · PATCH / DELETE /v1/notes/{id}Notes (decisions, findings)

kind is one of APP WEB_SERVICE COMMERCE LOCAL_STORE CREATOR B2B EVENT OTHER; goals[] of INSTALLS SIGNUPS PURCHASES VISITS LEADS FOLLOWERS AWARENESS.

bash
curl https://api.poremu.ai/v1/products \
  -H "Authorization: Bearer $POREMU_API_KEY" -H "Content-Type: application/json" \
  -d '{"workspaceId":"my-workspace","name":"Lumo","kind":"APP","url":"https://lumo.app","goals":["INSTALLS"]}'

Strategy

EndpointBody / query → result
GET /v1/strategy-templates?kind=&goals=a,b&budget=&lifecycle=&category={ recommended: StrategyTemplate[], all: StrategyTemplate[] } — 100 templates, scored for the given product shape
GET /v1/strategy-templates/{slug}StrategyTemplate with stages, triggers, automations
POST /v1/products/{id}/apply-template{ templateId, from?, replaceCalendar? } → ProductDetailDto. Creates the funnel, strategy-doc drafts, manual channels, calendar drafts and content drafts
POST /v1/products/{id}/analyze{ urls?, notes?, overwriteHuman?, applyTemplate?, engine?, model? } → AgentRunDto (QUEUED, runs in the background). Writes the seven sections, audiences and brand tone; sections a person edited are kept unless overwriteHuman
GET /v1/products/{id}/analysisProductAnalysisDto — sources read, facts, summary, when it last ran
GET /v1/products/{id}/strategy?section=&history=1StrategyDocDto[] (latest per section by default)
POST /v1/products/{id}/strategy{ section, title, body, data? } → new version. data is validated per section (positioning messaging audience channel_mix kpi budget plan)

The analyser needs an LLM: the workspace engine must be CREDITS or BYO_KEY. A workspace in BYO_MCP mode gets 400 VALIDATION_ERROR — there the client does the thinking with analysis_brief → save_strategy over MCP.

Channels, connections & metrics

EndpointBody / query → result
GET / POST /v1/products/{id}/channelsChannelDto[] / { kind, name?, role?, connectionId?, externalIds?, budgetCapKrwDaily?, note? }. With connectionId + externalIds the channel is CONNECTED and the last 30 days sync in the background
PATCH / DELETE /v1/channels/{id} · POST /v1/channels/{id}/syncUpdate, remove, re-sync the last 7 days (CONNECTOR_SYNC 1 credit; manual channels → 400)
GET /v1/connectors?workspaceId=ConnectorDto[] — Meta Ads, Google Ads, GA4, Google Sheets, Naver Search Ads… with available
GET /v1/connections/oauth/{provider}/start?workspaceId=&kind=&returnTo=Browser session + admin: redirects to Meta / Google consent
GET / POST /v1/connections?workspaceId= · DELETE /v1/connections/{id} · POST /v1/connections/{id}/test · GET /v1/connections/{id}/accountsForm connectors (API keys), test, list ad accounts / properties
GET /v1/metrics?workspaceId=&productId=&from=&to=&groupBy=&channelId=MetricsSummaryDto — totals for the period and the previous period of the same length; ctr cpc cpa cpi roas recomputed from sums (ctr is a ratio: 0.018 = 1.8 %)
POST /v1/metrics · POST /v1/metrics/import{ productId, channelId, date, metrics, scopeType?, scopeId?, scopeName? } upsert / { productId, rows[] } → { count }
GET /v1/products/{id}/funnel?from=&to=FunnelDto — stages, rates, bottleneck
GET / POST /v1/products/{id}/campaigns · PATCH / DELETE /v1/campaigns/{id}Campaigns

Connected channels sync daily at 04:00 KST (1 credit per channel to whoever created the connection). Token problems set the connection to NEEDS_REAUTH and fire the connection.needs_reauth webhook.

Calendar

EndpointBody / query → result
GET /v1/calendar/events?workspaceId=&from=&to=&productId=&kinds=a,b&channelId=&assigneeId=&includeDrafts=1CalendarEventDto[]. Recurring events are expanded server-side; an expanded instance's id is {id}:{date}
POST /v1/calendar/events{ workspaceId, title, startsAt, productId?, kind?, note?, endsAt?, allDay?, channelId?, campaignId?, contentItemId?, assigneeIds?, status?, draft?, color?, rrule? }
PATCH / DELETE /v1/calendar/events/{id} · POST /v1/calendar/events/{id}/confirmUpdate, delete, turn a draft into a confirmed event
POST /v1/calendar/fill{ workspaceId, productId, from?, to?, templateId? } → CalendarEventDto[] drafts — one week per template stage, routines as rrule; from defaults to next Monday 00:00 KST

kind is CAMPAIGN PUBLISH LAUNCH REPORT TASK MILESTONE REMINDER; status is planned doing done canceled.

Contents

EndpointBody / query → result
GET /v1/contents?workspaceId=&productId=&status=&campaignId=&cursor=&limit=paged ContentItemDto
POST /v1/products/{id}/contents{ title, format?, spec?, body?, status?, campaignId?, channelId?, assetIds?, generationIds?, scheduledAt?, parentId? }. Text passes the safety check; warnings[] flags superlatives, guarantees and medical claims
GET / PATCH / DELETE /v1/contents/{id}
POST /v1/contents/{id}/schedule{ scheduledAt, channelId? } → content + a PUBLISH calendar event

format: COPY IMAGE VIDEO CAROUSEL POST ARTICLE EMAIL SCRIPT LANDING. status moves IDEA → DRAFT → REVIEW → APPROVED → SCHEDULED → PUBLISHED (or REJECTED / ENDED); publishing to a channel is an approval (PUBLISH_POST). To generate the media itself use Images / Videos and pass generationIds.

Approvals, proposals & rules

EndpointBody / query → result
GET /v1/approvals?workspaceId=&status=&productId=&cursor=paged ApprovalDto (status accepts a comma list, e.g. APPROVED,REJECTED)
POST /v1/approvals{ workspaceId, action, summary, productId?, channelId?, payload?, evidence?, expiresInHours? } → ApprovalDto (PENDING; expires after 72 h by default)
GET /v1/approvals/{id} · POST /v1/approvals/{id}/decide{ decision: "approve" | "reject", note? } (admin). Approve → the executor runs (EXECUTED) or fails with a reason
GET / POST /v1/auto-approve-rules?workspaceId= · PATCH / DELETE /v1/auto-approve-rules/{id}{ workspaceId, action, productId?, channelKind?, limits?, enabled? } — auto-approve small changes within limits
GET /v1/proposals?workspaceId=&status=&productId=&cursor= · GET /v1/proposals/{id}paged ProposalDto — suggestions the AI left for review
POST /v1/proposals/{id}/decide{ decision: "accept" | "dismiss" }. Accepting converts suggested actions into approvals, calendar fills or content drafts
GET /v1/inbox/counts?workspaceId=Badge counts

action: BUDGET_CHANGE BID_CHANGE PAUSE_RESUME PUBLISH_POST SEND_EMAIL SEND_MESSAGE CREATE_CAMPAIGN UPDATE_CREATIVE WEBHOOK_OUT CUSTOM. Executors exist for BUDGET_CHANGE ({ level: "campaign" | "adset", externalId, dailyBudgetKrw }) and PAUSE_RESUME ({ level, externalId, status: "PAUSED" | "ACTIVE" }) on Meta Ads, Google Ads and Naver Search Ads (2 credits per execution), and for WEBHOOK_OUT. evidence is { dataSnapshot?, rule?, expectedEffect?, risks?, reversible?, notes? } — fill it so the approver can decide at a glance.

bash
curl https://api.poremu.ai/v1/approvals \
  -H "Authorization: Bearer $POREMU_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "workspaceId": "my-workspace",
    "productId": "PRODUCT_ID",
    "channelId": "CHANNEL_ID",
    "action": "BUDGET_CHANGE",
    "summary": "Cut Meta daily budget ₩120,000 → ₩96,000 (CPA ₩11,400 > target ₩9,000)",
    "payload": { "level": "campaign", "externalId": "2384…", "dailyBudgetKrw": 96000 },
    "evidence": { "expectedEffect": { "metric": "cpa", "from": 11400, "to": 9500 }, "reversible": true }
  }'

Approval lifecycle events (approval.requested, approval.decided, approval.executed) are delivered as webhooks.

Reports

EndpointBody / query → result
GET /v1/reports?workspaceId=&productId=&cursor= · GET /v1/reports/{id} · POST /v1/reports/{id}/readpaged ReportDto
POST /v1/reports/generate{ workspaceId, periodStart, periodEnd, productId?, title? } → ReportDto — numbers, channels and trend sections, an LLM summary (gpt-5.4-mini), a share link. 5 platform credits
GET /v1/reports/shared/{token}Public share view
GET / POST /v1/report-schedules?workspaceId= · PATCH / DELETE /v1/report-schedules/{id}{ workspaceId, name, cron, productId?, kind?, timezone?, deliver?, template?, enabled? }. cron is 5 fields or a preset such as weekly_mon_09; delivery in-app, by webhook (report.created) or by email to workspace members

For anything richer than a scheduled report — conditions, AI runs, Slack, email with templated text — build an automation.

AI marketer (agent)

EndpointBody / query → result
POST /v1/agent/chat{ workspaceId, message, productId?, sessionId?, engine?, model? } → SSE when Accept: text/event-stream: session {id} · delta {text} · tool {step} · approval {approval} · done {run, message, balance} · error {code,message}
POST /v1/agent/runs{ workspaceId, goal, productId?, engine?, model?, playbook? } → AgentRunDto (background; up to 12 tool steps / 4 minutes)
GET /v1/agent/runs?workspaceId=&productId=&chatSessionId=&cursor= · GET /v1/agent/runs/{id} · POST /v1/agent/runs/{id}/cancelRuns with their steps[], result and settled credits
GET /v1/agent/toolsThe tool catalog the agent (and MCP) can call: { name, description, kind, action?, platformCredits }[]
GET / PATCH /v1/engine?workspaceId=AiEngineSettingDto — CREDITS (Poremu models), BYO_KEY (your OpenAI / Gemini / Anthropic / xAI key, encrypted) or BYO_MCP (an assistant connected over MCP does the reasoning)
GET /v1/usage?workspaceId=&from=&to= · GET /v1/usage/logs?workspaceId=&cursor=What the AI spent, per tool
GET /v1/home?workspaceId=&productId=&range=7dHomeOverviewDto — the dashboard in one call

The agent uses the same tools listed on the MCP page. In BYO_MCP mode /agent/chat answers 400 VALIDATION_ERROR — use the assistant you connected instead.

js
const res = await fetch('https://api.poremu.ai/v1/agent/chat', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.POREMU_API_KEY}`, 'Content-Type': 'application/json', Accept: 'text/event-stream' },
  body: JSON.stringify({ workspaceId: 'my-workspace', productId, message: 'Which channel should get more budget next week, and why?' }),
});
for await (const chunk of res.body) process.stdout.write(new TextDecoder().decode(chunk)); // event: delta / tool / approval / done

Inbound hooks

To push your own events — orders, sign-ups, metric rows — into a product, create an inbound hook (POST /v1/hooks) and POST to its token URL. Details in Webhooks → Inbound hooks.

Errors & limits

The usual error envelope applies. Marketing-specific cases:

CodeWhen
403 FORBIDDENNot a member of the workspace, or the action needs OWNER / ADMIN (deciding approvals, connections, rules, hooks)
400 VALIDATION_ERROR with details.issuesInvalid strategy data, an automation / calendar body that fails validation, or an engine mode that cannot run this route
402 INSUFFICIENT_CREDITSThe run, report or generation could not be reserved
409 CONFLICTA run is already in flight for this resource
429 RATE_LIMITED60 requests / minute per key; daily run limits on automations