{"openapi":"3.0.3","info":{"title":"Oppizi Client API","version":"1.0.0","description":"## Authentication\nAuthenticate every request with your client key as a Bearer token: `Authorization: Bearer <client-jwt>`. Keys are provisioned by the Oppizi team.\n\n```sh\ncurl -H \"Authorization: Bearer <client-jwt>\" \"https://developer-api.oppizi.com/client/campaigns\"\n```\n\n## Scope\nEvery endpoint is always scoped to YOUR client. Reads (campaigns, performance, missions) only ever return your own data, and writes (tracking events) are recorded against your own client — cross-client access is not possible (a resource that is not yours returns 404, and an explicit cross-client filter returns 403).\n\n## Response envelope\nResponses are JSON shaped `{ \"success\": boolean, \"data\": ... }`. List endpoints use `{ \"data\": { \"nodes\": [...], \"totalCount\": N } }` with `first` (page size, ≤500) + `skip` (offset) pagination.\n\n## Status codes\n- **200** — OK\n- **400** — Invalid input (e.g. non-numeric id)\n- **401** — Missing/invalid client key\n- **403** — Not a client key, or cross-client access denied\n- **404** — Resource not found (or not yours)\n- **502** — Upstream error — safe to retry"},"servers":[{"url":"https://developer-api.oppizi.com"}],"security":[{"ClientBearer":[]}],"components":{"securitySchemes":{"ClientBearer":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Client-scoped key as a Bearer JWT; always pinned to your own client."}}},"tags":[{"name":"Client API","description":"Public, client-key-only reads. Curated fields; always scoped to your own client (cross-client access is impossible)."}],"paths":{"/client/campaigns":{"get":{"operationId":"clientCampaigns","summary":"Your campaigns","description":"Lists the calling client’s own campaigns as { nodes, totalCount }. Curated fields only — no cost, budget, payout, margin or billing data. Always scoped to your client; a cross-client ?clientId=<other> is denied with 403. Filters: ids, types, statuses; free-text search on name; date range ?from=&to= on start_date; paginate with first (≤500) + skip.\n\nNotes:\n- Cost/budget/payout/margin/billing fields are intentionally never returned.\n\nComposed from: direct PG SELECT (curated allowlist) via ops/entityRead.ts, forced client_id = key.clientId","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[{"name":"statuses","in":"query","required":false,"description":"Campaign status codes (0=DRAFT,10=ACTIVE,15=COMPLETED,20=ARCHIVED). Singular alias: status.","schema":{"type":"string"}},{"name":"types","in":"query","required":false,"description":"Campaign type codes (1=HANDTOHAND,2=LETTERBOX,3=DIRECTMAIL,4=INSERTS). Singular alias: type.","schema":{"type":"string"}},{"name":"search","in":"query","required":false,"description":"Free-text ILIKE on campaign name.","schema":{"type":"string"}},{"name":"first","in":"query","required":false,"description":"Page size 1..500 (default 50).","schema":{"type":"string"}},{"name":"skip","in":"query","required":false,"description":"Page offset (default 0).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"nodes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"type":"number"},"status":{"type":"number"},"client_id":{"type":"string"},"country_id":{"type":"string"},"country_name":{"type":"string"},"start_date":{"type":"string"},"campaign_duration_week":{"type":"number"},"offer_expiration_date":{"type":"string"},"no_expiration":{"type":"boolean"},"scan_data_enabled":{"type":"boolean"},"conversions_api_enabled":{"type":"boolean"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"typeEnum":{"type":"string"},"statusEnum":{"type":"string"}}}},"totalCount":{"type":"number"}}}}},"example":{"success":true,"data":{"nodes":[{"id":"9114","name":"Spring H2H — Chicago","type":1,"status":10,"client_id":"4101","country_id":"14","country_name":"United States","start_date":"2026-03-02","campaign_duration_week":13,"offer_expiration_date":"2026-07-01","no_expiration":false,"scan_data_enabled":true,"conversions_api_enabled":true,"created_at":"2026-01-14T09:22:31.000Z","updated_at":"2026-06-20T11:05:02.000Z","typeEnum":"HANDTOHAND","statusEnum":"ACTIVE"}],"totalCount":1}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/campaigns/{id}":{"get":{"operationId":"clientCampaignById","summary":"One of your campaigns","description":"Returns a single campaign (curated fields) only if it belongs to your client. A campaign owned by another client returns 404 — indistinguishable from a non-existent id, so ownership never leaks.\n\nNotes:\n- 404 for an unknown id OR a campaign that is not yours.\n\nComposed from: direct PG SELECT (curated allowlist) WHERE id = :id AND client_id = key.clientId","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Numeric campaign id (must belong to the calling key’s client).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"type":{"type":"number"},"status":{"type":"number"},"client_id":{"type":"string"},"country_id":{"type":"string"},"country_name":{"type":"string"},"start_date":{"type":"string"},"campaign_duration_week":{"type":"number"},"offer_expiration_date":{"type":"string"},"no_expiration":{"type":"boolean"},"scan_data_enabled":{"type":"boolean"},"conversions_api_enabled":{"type":"boolean"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"typeEnum":{"type":"string"},"statusEnum":{"type":"string"}}}}},"example":{"success":true,"data":{"id":"9114","name":"Spring H2H — Chicago","type":1,"status":10,"client_id":"4101","country_id":"14","country_name":"United States","start_date":"2026-03-02","campaign_duration_week":13,"offer_expiration_date":"2026-07-01","no_expiration":false,"scan_data_enabled":true,"conversions_api_enabled":true,"created_at":"2026-01-14T09:22:31.000Z","updated_at":"2026-06-20T11:05:02.000Z","typeEnum":"HANDTOHAND","statusEnum":"ACTIVE"}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/campaigns/{id}/performance":{"get":{"operationId":"clientCampaignPerformance","summary":"Campaign performance (volume only)","description":"Distribution / scan / conversion-COUNT series for your own campaign, bucketed over time (xAxis) with volume series (yAxis: flyersDistributed, scans, conversions, …). Ownership is checked first (404 if the campaign is not yours). Monetary series (cost, revenue, ROI, CPA, margin) are stripped by an allowlist and never returned.\n\nNotes:\n- yAxis is an allowlist: any monetary series present upstream is dropped.\n\nComposed from: ownership check (PG) + oppizi-api-dashboards /sales-ai/dashboard/:id/data_overview, curated to a volume-only allowlist","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Numeric campaign id (must belong to the calling key’s client).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"xAxis":{"type":"object","properties":{"label":{"type":"string"},"data":{"type":"array","items":{"type":"string"}}}},"yAxis":{"type":"object","properties":{"scans":{"type":"object","properties":{"label":{"type":"string"},"data":{"type":"array","items":{"type":"number"}}}},"conversions":{"type":"object","properties":{"label":{"type":"string"},"data":{"type":"array","items":{"type":"number"}}}},"flyersDistributed":{"type":"object","properties":{"label":{"type":"string"},"data":{"type":"array","items":{"type":"number"}}}}}}}}}},"example":{"success":true,"data":{"xAxis":{"label":"Week","data":["2026-W20","2026-W21"]},"yAxis":{"scans":{"label":"Scans","data":[120,145]},"conversions":{"label":"Conversions","data":[18,22]},"flyersDistributed":{"label":"Flyers Distributed","data":[20000,25000]}}}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/missions":{"get":{"operationId":"clientMissions","summary":"Your missions","description":"Lists the calling client’s own missions as { nodes, totalCount }, filterable by ?campaignId= (must be your campaign — a foreign campaign id simply matches nothing). Curated fields only: no distributor identity, GPS coordinates, or BA pay/cost. Filters: ids, statuses, types, campaignId; date range on time_range_start; first/skip.\n\nNotes:\n- Distributor identity (name/phone/email/national id/bank/DOB), GPS coordinates and BA pay/rate/cost are intentionally never returned.\n\nComposed from: direct PG SELECT (curated allowlist) via ops/entityRead.ts, forced owning-campaign client = key.clientId","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[{"name":"campaignId","in":"query","required":false,"description":"Restrict to one of your campaigns (alias: campaignIds). A campaign that is not yours yields an empty page.","schema":{"type":"string"}},{"name":"statuses","in":"query","required":false,"description":"Mission status codes (0=DRAFT,10=AVAILABLE,…,40=COMPLETED,60=PAID). Singular alias: status.","schema":{"type":"string"}},{"name":"first","in":"query","required":false,"description":"Page size 1..500 (default 50).","schema":{"type":"string"}},{"name":"skip","in":"query","required":false,"description":"Page offset (default 0).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"nodes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"number"},"type":{"type":"number"},"campaign_id":{"type":"string"},"campaign_name":{"type":"string"},"city_id":{"type":"string"},"city_name":{"type":"string"},"location_id":{"type":"string"},"time_range_start":{"type":"string"},"available_date":{"type":"string"},"check_in_time":{"type":"string"},"checkout_time":{"type":"string"},"checkout_flyer_quantity":{"type":"number"},"cancelled":{"type":"boolean"},"created_at":{"type":"string"},"updated_at":{"type":"string"},"statusEnum":{"type":"string"},"typeEnum":{"type":"string"}}}},"totalCount":{"type":"number"}}}}},"example":{"success":true,"data":{"nodes":[{"id":"773120","status":40,"type":1,"campaign_id":"9114","campaign_name":"Spring H2H — Chicago","city_id":"87","city_name":"Chicago","location_id":"83471","time_range_start":"2026-05-12T09:00:00.000Z","available_date":"2026-05-12","check_in_time":"2026-05-12T09:03:00.000Z","checkout_time":"2026-05-12T12:58:00.000Z","checkout_flyer_quantity":1980,"cancelled":false,"created_at":"2026-05-01T08:00:00.000Z","updated_at":"2026-05-12T13:00:00.000Z","statusEnum":"COMPLETED","typeEnum":"HANDTOHAND"}],"totalCount":1}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/campaigns/{id}/coupons":{"get":{"operationId":"clientCampaignCoupons","summary":"Your campaign coupons","description":"Lists the flyer/promo codes (Inventory items) for your OWN campaign. Use a row’s `code` as the `coupon` when recording a tracking event (POST /tracking/events). Always scoped to your client — a campaign that is not yours returns an empty page. Paginate with first (≤500) + skip.\n\nNotes:\n- Each `code` is a distributed flyer/promo code — send it as `coupon` on POST /tracking/events to attribute a conversion.\n\nComposed from: direct PG SELECT (curated allowlist) on \"Inventory\", forced client_id = key.clientId, filtered campaign_id = :id","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Numeric campaign id (must belong to the calling key’s client).","schema":{"type":"string"}},{"name":"first","in":"query","required":false,"description":"Page size 1..500 (default 50).","schema":{"type":"string"}},{"name":"skip","in":"query","required":false,"description":"Page offset (default 0).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"nodes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"code":{"type":"string"},"tag":{"type":"string"},"item_type_id":{"type":"string"},"campaign_id":{"type":"string"},"created_at":{"type":"string"},"updated_at":{"type":"string"}}}},"totalCount":{"type":"number"}}}}},"example":{"success":true,"data":{"nodes":[{"id":"55231","code":"CHI-9114-014","tag":"H2H batch A","item_type_id":"3","campaign_id":"9114","created_at":"2026-02-01T09:00:00.000Z","updated_at":"2026-02-01T09:00:00.000Z"}],"totalCount":1}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/campaigns/{id}/cost":{"get":{"operationId":"clientCampaignCost","summary":"Your campaign price","description":"The price of your OWN campaign as shown in the Oppizi UI — subtotal, tax, total, any discount (amountOff/percentOff), plus the currency sign. Only your client’s campaign; a campaign that is not yours returns 404. Oppizi-internal cost/margin fields are never included.\n\nNotes:\n- For an ad-hoc quote BEFORE a campaign exists, use the calculators (client-calc-*).\n\nComposed from: ownership check (Campaign.client_id = key.clientId) then sales-ai /cost, curated to a client-price allowlist + currency","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Numeric campaign id (must belong to the calling key’s client).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"subtotal":{"type":"number"},"tax":{"type":"number"},"totalCost":{"type":"number"},"amountOff":{"type":"number"},"currencySign":{"type":"string"}}}}},"example":{"success":true,"data":{"subtotal":3200,"tax":320,"totalCost":3520,"amountOff":0,"currencySign":"$"}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/campaigns/{id}/cost-preview":{"get":{"operationId":"clientCampaignCostPreview","summary":"Your campaign price estimate (pre-submit)","description":"A read-only ESTIMATE of what your OWN draft campaign would cost, computed the same way the Oppizi Ads builder shows a running estimate before you submit — quantity, per-piece price, subtotal, tax and total in your currency. This is your client-facing price (same class as your campaign price), never an Oppizi delivery cost. Only your client’s campaign; a campaign that is not yours returns 404.\n\nNotes:\n- Estimate only — the billed amount is client-campaign-cost once the campaign is charged. Oppizi delivery-cost/margin fields are never returned.\n\nComposed from: ownership check (Campaign.client_id = key.clientId) then the /ai cost-preview estimator, curated to a client-price allowlist","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Numeric campaign id (must belong to the calling key’s client).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"campaignId":{"type":"string"},"clientId":{"type":"string"},"channel":{"type":"string"},"subtype":{"type":"string"},"format":{"type":"string"},"quantity":{"type":"number"},"perPiece":{"type":"number"},"subtotal":{"type":"number"},"tax":{"type":"number"},"totalCost":{"type":"number"},"currency":{"type":"string"},"isEstimate":{"type":"boolean"},"note":{"nullable":true}}}}},"example":{"success":true,"data":{"campaignId":"9114","clientId":"450","channel":"EDDM","subtype":"EDDM","format":"BEST_SELLER","quantity":1706,"perPiece":0.4941,"subtotal":842.93,"tax":0,"totalCost":842.93,"currency":"$","isEstimate":true,"note":null}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/campaigns/{id}/analytics/{slug}":{"get":{"operationId":"clientCampaignAnalytics","summary":"Your campaign analytics view","description":"A curated analytics view for your OWN campaign. `slug` selects the view: overview (volume series over time), by-week and by-city (per-period / per-territory tables), ab-test (flyer-variant table), timeline (mission progress) and creative (your artwork performance). Includes your OWN value figures (revenue, ROI, CPA, spend/total and cost-per-scan) where available; Oppizi cost-to-deliver / margin figures (hard cost, distribution cost, BA payout) are stripped by an allowlist. Ownership is checked first — a campaign that is not yours returns 404.\n\nNotes:\n- Curated by an allowlist: any Oppizi cost-to-deliver / margin series (hardCost, distributionCost, baPayout, margin) is dropped.\n\nComposed from: ownership check (PG) then the matching /ai analytics upstream, curated to a value/volume allowlist (Oppizi-cost keys dropped)","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Numeric campaign id (must belong to the calling key’s client).","schema":{"type":"string"}},{"name":"slug","in":"path","required":true,"description":"Analytics view: overview | by-week | by-city | timeline | creative | ab-test.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"xAxis":{"type":"object","properties":{"label":{"type":"string"},"data":{"type":"array","items":{"type":"string"}}}},"yAxis":{"type":"object","properties":{"scans":{"type":"object","properties":{"label":{"type":"string"},"data":{"type":"array","items":{"type":"number"}}}},"conversions":{"type":"object","properties":{"label":{"type":"string"},"data":{"type":"array","items":{"type":"number"}}}},"flyersDistributed":{"type":"object","properties":{"label":{"type":"string"},"data":{"type":"array","items":{"type":"number"}}}}}}}}}},"example":{"success":true,"data":{"xAxis":{"label":"Week","data":["2026-W20","2026-W21"]},"yAxis":{"scans":{"label":"Scans","data":[120,145]},"conversions":{"label":"Conversions","data":[18,22]},"flyersDistributed":{"label":"Flyers Distributed","data":[20000,25000]}}}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/campaigns/{id}/scans":{"get":{"operationId":"clientCampaignScans","summary":"Your campaign scan analytics","description":"QR/scan analytics for your OWN campaign — unique scans, scan CVR, flyers distributed/inserted, inserting progress and average time between scans. Ownership is checked first (404 if the campaign is not yours). Monetary figures (e.g. cost-per-scan derived from Oppizi hard cost) are dropped.\n\nNotes:\n- The \"Scans CPA\" (an Oppizi-cost amount) is intentionally dropped.\n\nComposed from: ownership check (PG) then the /ai scans upstream, curated to a volume/CVR allowlist","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Numeric campaign id (must belong to the calling key’s client).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"quantity":{"type":"object","properties":{"title":{"type":"string"},"value":{"type":"number"}}},"cvr":{"type":"object","properties":{"title":{"type":"string"},"value":{"type":"number"},"type":{"type":"string"}}},"flyers":{"type":"object","properties":{"title":{"type":"string"},"value":{"type":"number"}}}}}}},"example":{"success":true,"data":{"quantity":{"title":"Unique Scans","value":342},"cvr":{"title":"Scans CVR","value":5.1,"type":"PERCENT"},"flyers":{"title":"Flyers distributed","value":20000}}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/campaigns/{id}/status-tracking":{"get":{"operationId":"clientCampaignStatusTracking","summary":"Your campaign status timeline","description":"The ordered history of status changes for your OWN campaign (status + timestamp). Ownership is checked first (404 if the campaign is not yours). Internal actor/user ids are never returned.\n\nComposed from: ownership check (PG) then the /ai status-tracking upstream, curated to status/timestamp fields","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Numeric campaign id (must belong to the calling key’s client).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"status":{"type":"string"},"changedAt":{"type":"string"}}}}}},"example":{"success":true,"data":[{"status":"ACTIVE","changedAt":"2026-03-04T00:00:00Z"}]}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/campaigns/{id}/dates":{"get":{"operationId":"clientCampaignDates","summary":"Your campaign schedule","description":"Schedule + status for your OWN campaign — start date, status, active date ranges, and the derived planned end date / actual duration weeks / client industry. Ownership is checked first (404 if not yours).\n\nComposed from: ownership check (PG) then the /ai dates upstream, curated to an explicit allowlist","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Numeric campaign id (must belong to the calling key’s client).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"startDate":{"type":"string"},"status":{"type":"string"},"activeDates":{"type":"object","properties":{"start":{"nullable":true},"end":{"nullable":true}}},"client_industry":{"type":"string"},"actual_duration_weeks":{"type":"number"},"planned_end_date":{"type":"string"}}}}},"example":{"success":true,"data":{"startDate":"1780002000000","status":"ACTIVE","activeDates":{"start":null,"end":null},"client_industry":"RETAIL","actual_duration_weeks":12,"planned_end_date":"2026-06-01"}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/campaigns/{id}/artworks":{"get":{"operationId":"clientCampaignArtworks","summary":"Your campaign creative","description":"The creative for your OWN campaign — flyer design (S3 link + thumbnail), logo and any additional artworks. Ownership is checked first (404 if the campaign is not yours).\n\nComposed from: ownership check (PG) then the /ai artworks upstream, curated to { flyer, logo, artworks }","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[{"name":"id","in":"path","required":true,"description":"Numeric campaign id (must belong to the calling key’s client).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"flyer":{"type":"object","properties":{"s3Link":{"type":"string"},"thumbnailUrl":{"type":"string"},"fileName":{"type":"string"},"designSource":{"type":"string"},"isUploaded":{"type":"boolean"}}},"logo":{"nullable":true},"artworks":{"type":"array","items":{}}}}}},"example":{"success":true,"data":{"flyer":{"s3Link":"https://…/design.pdf","thumbnailUrl":"https://…/design.jpeg","fileName":"My design","designSource":"upload","isUploaded":true},"logo":null,"artworks":[]}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/conversions":{"get":{"operationId":"clientConversions","summary":"Your conversions","description":"Your OWN conversion records — the read-back of the conversions you write via /tracking/events. Always scoped to your client (cross-client is not possible). Filter by from/to (timestamps), metricIds, type; paginate with limit + skip. Internal payout/finance fields are never returned.\n\nNotes:\n- Payout/finance fields (datePaid, paidWeek, paidYear, invoiceUrl, bonusDisqualified) are intentionally dropped.\n\nComposed from: the /ai conversions service read, forced to key.clientId, curated to a client-safe allowlist","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[{"name":"from","in":"query","required":false,"description":"Start of the timestamp range.","schema":{"type":"string"}},{"name":"to","in":"query","required":false,"description":"End of the timestamp range.","schema":{"type":"string"}},{"name":"metricIds","in":"query","required":false,"description":"Filter by metric id(s), comma-separated.","schema":{"type":"string"}},{"name":"type","in":"query","required":false,"description":"Filter by conversion type.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Page size.","schema":{"type":"string"}},{"name":"skip","in":"query","required":false,"description":"Page offset.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"metricId":{"type":"string"},"type":{"type":"string"},"timestamp":{"type":"string"},"clientId":{"type":"string"},"metadata":{"nullable":true}}}}}},"example":{"success":true,"data":[{"id":"6571c673b1542cad55020ee4","metricId":"509","type":"CONVERSION","timestamp":"2026-06-08T08:31:35.000Z","clientId":"450","metadata":null}]}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/cities":{"get":{"operationId":"clientCities","summary":"Self-serve cities","description":"Cities you can target in a self-serve campaign, with the fields needed for targeting: id, name, state, nameWithState, country, latitude, longitude. Only self-serve-enabled cities; ops-only fields (surge, pay, recruitment) are never exposed. Optional `search` on name; `first` (≤1000).\n\nNotes:\n- Pass rows as `selectedCities` to set_campaign_targeting when building a DM-core / H2H self-serve campaign.\n\nComposed from: direct PG SELECT on \"City\" (minimal geo/label columns) WHERE the city has a self-serve \"Location\" (Location.is_self_serve_location = true)","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[{"name":"search","in":"query","required":false,"description":"Case-insensitive city-name substring.","schema":{"type":"string"}},{"name":"first","in":"query","required":false,"description":"Max rows (default 500, ≤1000).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"nodes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"state":{"type":"string"},"nameWithState":{"type":"string"},"country_id":{"type":"string"},"country_name":{"type":"string"},"latitude":{"type":"string"},"longitude":{"type":"string"}}}},"totalCount":{"type":"number"}}}}},"example":{"success":true,"data":{"nodes":[{"id":"55","name":"Chicago","state":"IL","nameWithState":"Chicago, IL","country_id":"1","country_name":"United States","latitude":"41.8781","longitude":"-87.6298"}],"totalCount":1}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/designs":{"get":{"operationId":"clientDesigns","summary":"Your flyer designs","description":"Your OWN flyer designs (paginated/filterable) to attach with select_campaign_design. clientId is forced from your key — you only ever see your own designs. Filters: search, format, status, sortBy, sortDirection; page + limit.\n\nNotes:\n- Use a design’s `id` as designId in select_campaign_design.\n\nComposed from: flyer service getDesigns(clientId), clientId forced from key.clientId","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[{"name":"search","in":"query","required":false,"description":"Design-name/text search.","schema":{"type":"string"}},{"name":"status","in":"query","required":false,"description":"Filter by design status.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"designs":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"clientId":{"type":"string"},"format":{"type":"string"},"status":{"type":"string"},"thumbnailUrl":{"type":"string"}}}},"pagination":{"type":"object","properties":{"currentPage":{"type":"number"},"totalPages":{"type":"number"},"totalItems":{"type":"number"}}}}}}},"example":{"success":true,"data":{"designs":[{"id":"812","clientId":"450","format":"POSTCARD_6X9","status":"READY","thumbnailUrl":"https://…/thumb.png"}],"pagination":{"currentPage":1,"totalPages":1,"totalItems":1}}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/audiences":{"get":{"operationId":"clientAudiences","summary":"Your audiences","description":"Your OWN reusable ADM audiences to link with link_campaign_audience. Always scoped to your client (cross-client → 403). Paginate with first (≤500) + skip; filter by ids/statuses; search on name.\n\nNotes:\n- Use an audience’s `id` as audienceId in link_campaign_audience.\n\nComposed from: direct PG SELECT on \"Audience\", forced client_scope = key.clientId","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[{"name":"first","in":"query","required":false,"description":"Page size 1..500 (default 50).","schema":{"type":"string"}},{"name":"skip","in":"query","required":false,"description":"Page offset (default 0).","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"nodes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"client_id":{"type":"string"},"status":{"type":"string"}}}},"totalCount":{"type":"number"}}}}},"example":{"success":true,"data":{"nodes":[{"id":"331","name":"Spring buyers","client_id":"450","status":"1"}],"totalCount":1}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/calculators/flyer-distribution/options":{"get":{"operationId":"clientCalcFlyerOptions","summary":"Flyer distribution options","description":"The selectable options (channels, cities/markets, formats) for the flyer-distribution price calculator — the same public calculator as the Oppizi website. No client data; safe to browse before you have a campaign.\n\nComposed from: public flyer-distribution calculator options","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{}}}},"example":{"success":true,"data":{}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/calculators/flyer-distribution/quantities":{"get":{"operationId":"clientCalcFlyerQuantities","summary":"Flyer quantity/price ladder","description":"The quantity → price ladder for the flyer-distribution calculator (client-facing prices). Public calculator; no client data.\n\nComposed from: public flyer-distribution calculator quantities","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{}}}},"example":{"success":true,"data":{}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/calculators/flyer-distribution":{"post":{"operationId":"clientCalcFlyerDistribution","summary":"Flyer distribution quote","description":"Compute a flyer-distribution price quote (client-facing price) from a selection — quantity, city/market, format, etc. Same public calculator the Oppizi website uses; POST carries the selection. No client data stored.\n\nComposed from: public flyer-distribution calculator","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"input":{"description":"Calculator selection (see the options/quantities endpoints)."}},"required":["input"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{}}}},"example":{"success":true,"data":{}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/client/calculators/eddm":{"post":{"operationId":"clientCalcEddm","summary":"EDDM cost quote","description":"Compute an EDDM (Every Door Direct Mail) price quote (client-facing price) for a given volume/selection. Public calculator; no client data.\n\nComposed from: public EDDM calculator","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"input":{"description":"EDDM selection, e.g. { volume }."}},"required":["input"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{}}}},"example":{"success":true,"data":{}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}},"/tracking/events":{"post":{"operationId":"createTrackingEvent","summary":"Create event","description":"Record a custom event (conversion, signup, purchase, business metric). `eventName` and `value` are required. Scoped to your own client account. Conversions are attributed to a distributed Oppizi promo/flyer code via `coupon` (or a `campaignId`/`metricId`); that code must be a real Oppizi coupon or the call returns 403 \"Coupon doesn't exist\". See the Recommended events guide for per-event required fields.\n\nComposed from: developer-api trackingEvents (DynamoDB OppiziEvents)","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"eventName":{"description":"Name of the event, e.g. \"Purchase\"."},"value":{"description":"Numeric value of the event."},"metadata":{"description":"Optional key/value object. Up to 50 entries; key ≤ 40 chars, value ≤ 256 chars."}},"required":["eventName","value"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"id":{"type":"string"},"eventName":{"type":"string"},"value":{"type":"number"},"metadata":{"type":"object","properties":{"orderId":{"type":"number"}}},"createdAt":{"type":"string"}}}}},"example":{"success":true,"data":{"id":"evt_9f21","eventName":"Purchase","value":250,"metadata":{"orderId":182394},"createdAt":"2026-07-01T12:00:00.000Z"}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}},"get":{"operationId":"listTrackingEvents","summary":"Get events","description":"Returns your events, newest first. Filter by name, value and metadata; paginate with a cursor.\n\nComposed from: developer-api trackingEvents (DynamoDB OppiziEvents)","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[{"name":"startingAfter","in":"query","required":false,"description":"Pagination cursor from the previous page.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"Number of events to return (1..100, default 20).","schema":{"type":"string"}},{"name":"eventName","in":"query","required":false,"description":"Filter by event name.","schema":{"type":"string"}},{"name":"value","in":"query","required":false,"description":"Filter by event value.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"nodes":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"eventName":{"type":"string"},"value":{"type":"number"},"metadata":{"type":"object","properties":{"orderId":{"type":"number"}}},"createdAt":{"type":"string"}}}},"totalCount":{"type":"number"}}}}},"example":{"success":true,"data":{"nodes":[{"id":"evt_9f21","eventName":"Purchase","value":250,"metadata":{"orderId":182394},"createdAt":"2026-07-01T12:00:00.000Z"}],"totalCount":1}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}},"put":{"operationId":"updateTrackingEvent","summary":"Update event","description":"Update an existing event. Same body shape as create; scoped to your own client account.\n\nComposed from: developer-api trackingEvents (DynamoDB OppiziEvents)","tags":["Client API"],"security":[{"ClientBearer":[]}],"parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"eventName":{"description":"Name of the event."},"value":{"description":"Numeric value of the event."},"metadata":{"description":"Optional key/value object (see Create event)."}},"required":["eventName","value"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"data":{"type":"object","properties":{"id":{"type":"string"},"eventName":{"type":"string"},"value":{"type":"number"},"metadata":{"type":"object","properties":{"orderId":{"type":"number"}}},"createdAt":{"type":"string"}}}}},"example":{"success":true,"data":{"id":"evt_9f21","eventName":"Purchase","value":300,"metadata":{"orderId":182394},"createdAt":"2026-07-01T12:00:00.000Z"}}}}},"400":{"description":"Invalid input (e.g. non-numeric campaignId, missing required query param)"},"401":{"description":"Missing/invalid credential"},"403":{"description":"Unauthenticated, insufficient permission tier, or cross-client access denied"},"404":{"description":"Campaign / resource not found"},"502":{"description":"Upstream (core API) error — safe to retry"}}}}}}