{"openapi":"3.1.0","info":{"title":"teia","version":"1.0.0","summary":"Cookieless ad serving for LOCO's sites and apps.","description":"Sites embed the tag (`/t.js`), which calls `/v1/serve` once per page view and reports events to `/v1/e`. Apps call the same endpoints and render the items themselves. Every served item carries a signed **offer token**; send it back in events. Clicks go through `/c/{offer}` (counted, then redirected). URLs in responses are relative to the base the request was sent to (`https://tabdj.com/_t/` through a site proxy, or this server). No cookies are read or set. See docs/architecture.md for how selection and counting work.","contact":{"name":"LOCO","email":"contact@loco.ltd","url":"https://useteia.com/"}},"servers":[{"url":"https://api.useteia.com","description":"The API"},{"url":"https://px.useteia.com","description":"Serving: the tag, /v1/serve, /v1/e and the click redirects"}],"tags":[{"name":"serving","description":"Choosing what to show."},{"name":"events","description":"Impressions, viewability and errors."},{"name":"clicks","description":"Counted redirects."},{"name":"tag","description":"The browser tag."},{"name":"requests","description":"Campaign requests and publisher applications."},{"name":"payments","description":"Checkout: Pix top-ups of an advertiser's balance, through marx."},{"name":"account","description":"The advertiser panel (useteia.com/painel). Cookie session `teia_account`; only the panel's origins may call it with credentials, and every write needs the header `X-Teia: 1`. Money in centavos; days are São Paulo days (YYYY-MM-DD)."},{"name":"meta","description":"Health and this document."}],"paths":{"/v1/serve":{"post":{"tags":["serving"],"operationId":"serve","summary":"Choose the items for every slot on a page view","description":"One call per page view for all of its slots, so items never repeat on the page. Accepts `application/json` or `text/plain` (the tag uses text/plain to avoid a CORS preflight). Any origin may call it. Body limit 64 KB.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServeRequest"},"examples":{"tag":{"summary":"A tabdj page (what the tag sends)","value":{"site":"tabdj","pv":"k3j9x2a7","slots":[{"id":"0","placement":"gear","y":1380,"above":false},{"id":"1","placement":"rail","y":180,"above":true},{"id":"2","placement":"top","y":0,"above":true}],"page":{"url":"https://tabdj.com/pt/guias/controladoras?utm_source=instagram","lang":"pt-BR","title":"Controladoras para começar","ref":"www.google.com","utm":{"source":"instagram"}},"vp":{"w":1440,"h":900}}},"app":{"summary":"An app (mund.im's bus ride)","value":{"site":"mundim","pv":"ride-5f2c","slots":[{"id":"bus","placement":"bus-ride","bp":"mobile"}],"page":{"lang":"pt-BR"},"ctx":{"place":"beach","route":"80"}}}}},"text/plain":{"schema":{"type":"string","description":"The same JSON, as text."}}}},"responses":{"200":{"description":"What to render, slot by slot (same order and ids as the request).","headers":{"Cache-Control":{"schema":{"type":"string","const":"no-store"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ServeResponse"},"examples":{"tag":{"summary":"The tabdj page","value":{"pv":"k3j9x2a7","slots":[{"id":"0","placement":"gear","items":[{"offer":"WzEsIlpGSGpG….I3uzaw2bCfcH","kind":"affiliate_card","img":"https://cdn.example/p/3f2a.webp","title":"Controladora DJ 2 canais","cta":"Ver oferta","href":"c/WzEsIlpGSGpG….I3uzaw2bCfcH","rel":"sponsored noopener"}],"fallback":null,"disclosure":"Links de afiliado","advertise":{"label":"Anuncie aqui","href":"c/a/WzEsIlpGSGpG….I3uzaw2bCfcH"},"size":{"w":720,"h":200}},{"id":"1","placement":"rail","items":[],"fallback":"advertise","disclosure":"Publicidade","advertise":{"label":"Anuncie aqui","href":"c/a/WzEsIk9ubTl3….Q2xvY2sx"},"size":{"w":300,"h":600}},{"id":"2","placement":"top","items":[{"offer":"WzEsIlRvcEJh….cjFiYXIx","kind":"bar","title":"Controladoras com 10% off só esta semana","cta":"Ver oferta","href":"c/WzEsIlRvcEJh….cjFiYXIx","rel":"sponsored noopener"}],"fallback":null,"disclosure":"Publicidade","advertise":{"label":"Anuncie aqui","href":"c/a/WzEsIlRvcEJh….cjFiYXIx"},"size":{"w":1008,"h":40},"bar":true}]}},"app":{"summary":"mund.im's bus ride (a pixel surface)","value":{"pv":"ride-5f2c","slots":[{"id":"bus","placement":"bus-ride","items":[{"offer":"WzEsIlJpZGU1….aW8xPixa","kind":"text","pixel":"https://cdn.example/c/9d1e.png","w":160,"h":90,"title":"pixta.me","body":"Pixel art from your photos","cta":"Try it","href":"c/WzEsIlJpZGU1….aW8xPixa","rel":"sponsored noopener"}],"fallback":null,"disclosure":"Publicidade","size":{"w":160,"h":90}}]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"description":"Unknown site key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"$ref":"#/components/responses/TooLarge"}}},"options":{"tags":["serving"],"operationId":"servePreflight","summary":"CORS preflight","responses":{"204":{"description":"Allowed for any origin."}}}},"/v1/e":{"post":{"tags":["events"],"operationId":"events","summary":"Report render, viewable and error events","description":"Usually sent with `navigator.sendBeacon` as text/plain. Each event names an offer token; invalid tokens are ignored. Accepted from beacons: `render`, `viewable`, `error`, within 1 h of the offer's issue. Clicks are counted by `/c/{offer}`, not here. Each (offer, type) counts once. Always answers 204.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EventBatch"},"example":{"e":[{"o":"<offer token>","t":"render"},{"o":"<offer token>","t":"viewable","ttv":820,"depth":0.35}]}},"text/plain":{"schema":{"type":"string","description":"The same JSON, as text."}}}},"responses":{"204":{"description":"Recorded (or ignored)."},"400":{"$ref":"#/components/responses/BadRequest"},"413":{"$ref":"#/components/responses/TooLarge"}}},"options":{"tags":["events"],"operationId":"eventsPreflight","summary":"CORS preflight","responses":{"204":{"description":"Allowed for any origin."}}}},"/c/{offer}":{"get":{"tags":["clicks"],"operationId":"click","summary":"Count a click and redirect to the landing page","description":"House and direct campaigns get UTM parameters (the campaign's template, else `utm_source=teia&utm_medium=<site>&utm_campaign=<campaign>&utm_content=<placement>-<creative>`). Affiliate links are passed through untouched. Suspicious clicks are flagged (`expired`, `no-impression`, `too-fast`, `bot`, `prefetch`), never dropped. An invalid token redirects to https://useteia.com/.","parameters":[{"$ref":"#/components/parameters/Offer"}],"responses":{"302":{"$ref":"#/components/responses/Redirect"}}}},"/c/a/{offer}":{"get":{"tags":["clicks"],"operationId":"advertiseClick","summary":"Count an \"advertise here\" click and redirect to the site's advertise page","description":"Redirects to the site's advertise URL with `utm_source=<site>&utm_medium=teia&utm_campaign=advertise-here&utm_content=<placement>`.","parameters":[{"$ref":"#/components/parameters/Offer"}],"responses":{"302":{"$ref":"#/components/responses/Redirect"}}}},"/t.js":{"get":{"tags":["tag"],"operationId":"tag","summary":"The browser tag","description":"Embed with `<script async src=\"/_t/t.js\" data-site=\"SITE\"></script>` and `<div data-teia=\"PLACEMENT\"></div>`. Cached 5 minutes, with an ETag.","responses":{"200":{"description":"The script.","content":{"application/javascript":{"schema":{"type":"string"}}}},"304":{"description":"Not modified (If-None-Match)."},"404":{"description":"The tag hasn't been built on this server."}}}},"/health":{"get":{"tags":["meta"],"operationId":"health","summary":"Liveness and database check","responses":{"200":{"description":"Up.","content":{"application/json":{"example":{"ok":true}}}},"503":{"description":"The database is unreachable.","content":{"application/json":{"example":{"ok":false,"db":"down"}}}}}}},"/v1/requests":{"post":{"tags":["requests"],"operationId":"createRequest","summary":"Send a campaign request or a publisher application","description":"What the onboarding wizards at useteia.com/anunciar and /publicar send. Nothing goes live from here: the request is stored for review and LOCO answers by email. Send `multipart/form-data` with a `payload` field (the JSON below) and, for campaign requests, up to 8 creative images as `file:<W>x<H>` (PNG, JPEG, WebP or AVIF, 3 MB each; stored privately as WebP), or send the JSON alone as `application/json`. At most 6 requests per client every 10 minutes.","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["payload"],"properties":{"payload":{"type":"string","description":"RequestPayload as JSON."}},"additionalProperties":{"type":"string","format":"binary","description":"Creative image for a size, under the field name `file:300x600`."}}},"application/json":{"schema":{"$ref":"#/components/schemas/RequestPayload"},"example":{"kind":"publisher","lang":"en","consent":true,"contact":{"name":"Sam","email":"sam@example.org"},"publisher":{"property":{"kind":"app","name":"Beat Quiz","url":"https://beatquiz.app","langs":["en"],"countries":["US"],"audience":"10k-50k"},"spots":[{"name":"Results screen","size":"300x250","where":"screen","devices":["mobile"]}],"rules":{"blocked":[],"approval":"each","empty":"advertise"},"payout":{"method":"paypal","entity":"person"}}}}}},"responses":{"201":{"description":"Stored. `ref` is the reference to quote in emails.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestCreated"},"example":{"ref":"T-7K2M9Q"}}}},"400":{"description":"A field fails validation; `field` names it (e.g. `contact.email`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldError"}}}},"413":{"description":"Body over 14 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"415":{"description":"Not multipart/form-data or JSON.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests from this client; see Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"options":{"tags":["requests"],"operationId":"requestsPreflight","summary":"CORS preflight","responses":{"204":{"description":"Allowed for any origin."}}}},"/v1/checkout":{"post":{"tags":["payments"],"operationId":"createCheckout","summary":"Send a campaign request and pay for it with Pix","description":"The last step of the advertiser wizard. Takes the same body as `POST /v1/requests` (campaign requests only) plus `billing.document` (CPF or CNPJ) and a `checkout_key` the client chooses. Stores the request, finds or creates the account by e-mail and opens a Pix charge at marx for `advertiser.budget.total` (BRL, R$ 50 to R$ 100.000). When it's paid, the amount is added to the account's balance. Sending the same `checkout_key` again returns the same payment instead of charging twice (and retries the charge if marx failed the first time).","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["payload"],"properties":{"payload":{"type":"string","description":"CheckoutPayload as JSON."}},"additionalProperties":{"type":"string","format":"binary","description":"Creative image for a size, under the field name `file:300x600`."}}},"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutPayload"},"example":{"kind":"advertiser","lang":"pt","consent":true,"checkout_key":"3f9c2a7e-wizard-1","billing":{"document":"529.982.247-25"},"contact":{"name":"Ana Souza","email":"ana@lojadodj.com.br","company":"Loja do DJ"},"advertiser":{"campaign":{"name":"Outubro","brand":"Loja do DJ","website":"lojadodj.com.br","goal":"traffic"},"audience":{"sites":["tabdj"],"placements":["tabdj:rail","tabdj:footer"],"langs":["pt"]},"creatives":{"format":"text","url":"https://lojadodj.com.br/controladoras","text":{"title":"Controladoras com 10% off","body":"Só esta semana.","cta":"Ver ofertas"}},"budget":{"model":"week","start":"2026-10-19","weeks":2,"total":600,"currency":"BRL"}}}}}},"responses":{"200":{"description":"This `checkout_key` was seen before: its payment, as it is now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Checkout"}}}},"201":{"description":"Stored, and the Pix charge is open.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Checkout"}}}},"400":{"description":"A field fails validation; `field` names it (e.g. `billing.document`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldError"}}}},"429":{"description":"Too many checkouts from this client; see Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"The request was stored but marx couldn't open the charge (`payment.status` is `failed`). Send the same body again, or renew, to retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Checkout"}}}},"503":{"description":"Checkout is off (marx isn't configured).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"options":{"tags":["payments"],"operationId":"checkoutPreflight","summary":"CORS preflight","responses":{"204":{"description":"Allowed for any origin."}}}},"/v1/checkout/{token}":{"get":{"tags":["payments"],"operationId":"getCheckout","summary":"The payment's status (poll while pending)","description":"Polled by the wizard every few seconds. While the payment is pending teia also asks marx, at most every 15 seconds, so a slow webhook doesn't keep the payer waiting.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"},"description":"`payment.token` from the checkout."}],"responses":{"200":{"description":"The payment.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Checkout"}}}},"404":{"description":"No payment with this token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/checkout/{token}/renew":{"post":{"tags":["payments"],"operationId":"renewCheckout","summary":"A new Pix for an expired or failed payment","description":"For the same request and amount. An expired payment gets a new one (new code and token); a failed one retries its charge. Pending or paid payments are returned as they are.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Nothing to renew: the payment as it is.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Checkout"}}}},"201":{"description":"A new Pix charge is open.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Checkout"}}}},"404":{"description":"No payment with this token.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"marx couldn't open the charge; try again.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Checkout"}}}}}}},"/v1/account/login":{"post":{"tags":["account"],"operationId":"accountLogin","summary":"E-mail a sign-in link (or, without an account, a sign-up link)","description":"When an account has this e-mail, a single-use link (15 minutes) to the panel goes out (`entrar?t=`, en `sign-in?t=`). Otherwise a single-use link to create the account (24 hours; `criar-conta?t=`, en `create-account?t=`). The answer is the same either way. At most 5 sign-in links per account every 15 minutes, 3 sign-up links per address every 24 hours (past those the answer stays 200 and nothing is sent), and 10 requests per client every 15 minutes. Outside production the link also comes back as `dev_link`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"},"lang":{"type":"string","enum":["pt","en"],"default":"pt"}}},"example":{"email":"ana@lojadodj.com.br","lang":"pt"}}}},"responses":{"200":{"description":"Accepted (a sign-in or sign-up link went out, unless a limit was reached).","content":{"application/json":{"schema":{"type":"object","required":["ok"],"properties":{"ok":{"const":true},"dev_link":{"type":"string"}}}}}},"400":{"description":"Not an e-mail address.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldError"}}}},"403":{"description":"Origin not allowed, or no X-Teia header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; see Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/account/signup":{"get":{"tags":["account"],"operationId":"accountSignupRead","summary":"The e-mail a sign-up link is for","description":"For the sign-up page: reading doesn't use the link up (mail scanners open links). `t` is the token from the e-mailed link.","parameters":[{"name":"t","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The link is valid and unused.","content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"410":{"description":"Unknown, used or expired link.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"const":"expired"}}}}}}}},"post":{"tags":["account"],"operationId":"accountSignup","summary":"Create the account from a sign-up link","description":"Uses the link up and creates the account with its e-mail (no CPF/CNPJ yet: the first top-up asks for it), then signs in like `/session` (cookie `teia_account`). If an account with that e-mail exists by then (a checkout, the team), it signs in to it unchanged and answers 200 with `existed: true`. Invalid fields answer 400 without using the link up. The address's other sign-up links stop working. 30 requests per client every 15 minutes.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["token","name"],"properties":{"token":{"type":"string"},"name":{"type":"string","minLength":1,"maxLength":120},"company":{"type":"string","maxLength":120},"phone":{"type":"string","maxLength":40},"lang":{"type":"string","enum":["pt","en"],"default":"pt"}}},"example":{"token":"…","name":"Ana Souza","company":"Loja do DJ","lang":"pt"}}}},"responses":{"200":{"description":"An account with this e-mail already existed: signed in to it.","content":{"application/json":{"schema":{"type":"object","required":["account","existed"],"properties":{"account":{"$ref":"#/components/schemas/Account"},"existed":{"const":true}}}}}},"201":{"description":"Account created and signed in.","content":{"application/json":{"schema":{"type":"object","required":["account"],"properties":{"account":{"$ref":"#/components/schemas/Account"}}}}}},"400":{"description":"A field is empty or too long; `field` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldError"}}}},"403":{"description":"Origin not allowed, or no X-Teia header.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"Unknown, used or expired link.","content":{"application/json":{"schema":{"type":"object","required":["error"],"properties":{"error":{"const":"expired"}}}}}},"429":{"description":"Too many requests; see Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/account/session":{"post":{"tags":["account"],"operationId":"accountSession","summary":"Sign in with a link's token, or once right after checkout","description":"`token` from the e-mailed link (single use), or `checkout`: the payment token of a paid checkout that created the account, within 24 hours, once. Sets the `teia_account` cookie (HttpOnly, SameSite=Lax, Path=/v1, 30 days, renewed while used).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"token":{"type":"string"},"checkout":{"type":"string"}}}}}},"responses":{"200":{"description":"Signed in.","content":{"application/json":{"schema":{"type":"object","required":["account"],"properties":{"account":{"$ref":"#/components/schemas/Account"}}}}}},"400":{"description":"Neither token nor checkout.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldError"}}}},"401":{"description":"The link is invalid, used or expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"This checkout may not open the panel: sign in with your e-mail.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many requests; see Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/account/logout":{"post":{"tags":["account"],"operationId":"accountLogout","summary":"Sign out","responses":{"204":{"description":"Signed out (the session is deleted)."}}}},"/v1/account/me":{"get":{"tags":["account"],"operationId":"accountMe","summary":"The account and its balance","security":[{"panelSession":[]}],"responses":{"200":{"description":"The signed-in account.","content":{"application/json":{"schema":{"type":"object","required":["account","balance_cents"],"properties":{"account":{"$ref":"#/components/schemas/Account"},"balance_cents":{"type":"integer"}}}}}},"401":{"description":"Signed out (no or expired session).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["account"],"operationId":"accountUpdate","summary":"Change name, company or phone","security":[{"panelSession":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"company":{"type":"string","maxLength":120},"phone":{"type":"string","maxLength":40}}}}}},"responses":{"200":{"description":"Saved.","content":{"application/json":{"schema":{"type":"object","required":["account"],"properties":{"account":{"$ref":"#/components/schemas/Account"}}}}}},"400":{"description":"A field is too long or empty.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldError"}}}},"401":{"description":"Signed out (no or expired session).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/account/overview":{"get":{"tags":["account"],"operationId":"accountOverview","summary":"Balance, results over time, campaigns and requests","security":[{"panelSession":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","enum":[7,30,90],"default":30},"description":"The last 7, 30 or 90 days (São Paulo), today included."}],"responses":{"200":{"description":"The overview.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Overview"}}}},"401":{"description":"Signed out (no or expired session).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/account/campaigns":{"get":{"tags":["account"],"operationId":"accountCampaigns","summary":"The account's campaigns with their results","security":[{"panelSession":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","enum":[7,30,90],"default":30},"description":"The last 7, 30 or 90 days (São Paulo), today included."}],"responses":{"200":{"description":"Campaigns, live first.","content":{"application/json":{"schema":{"type":"object","required":["campaigns"],"properties":{"campaigns":{"type":"array","items":{"$ref":"#/components/schemas/CampaignSummary"}}}}}}},"401":{"description":"Signed out (no or expired session).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/account/campaigns/{id}":{"get":{"tags":["account"],"operationId":"accountCampaign","summary":"A campaign: results by day, by placement and by creative","security":[{"panelSession":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"days","in":"query","required":false,"schema":{"type":"integer","enum":[7,30,90],"default":30},"description":"The last 7, 30 or 90 days (São Paulo), today included."}],"responses":{"200":{"description":"The campaign.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignDetail"}}}},"401":{"description":"Signed out (no or expired session).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not this account's (or doesn't exist).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["account"],"operationId":"accountCampaignUpdate","summary":"Rename, pause or resume, change the end date or the daily rate","description":"Pause a live campaign; resume one paused by you or by the balance when the balance covers a day (one paused by the team or still in review can't). `ends_at` is the last day it runs: today or later, and not before the start. `daily_cents` from 500 to 10000000 (for a CPM campaign, the most a day can cost); the campaign's share among paid campaigns follows it. Neither the pricing model nor the CPM price can be changed here.","security":[{"panelSession":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"status":{"type":"string","enum":["live","paused"]},"ends_at":{"type":"string","format":"date"},"daily_cents":{"type":"integer","minimum":500,"maximum":10000000}}},"example":{"status":"paused"}}}},"responses":{"200":{"description":"Saved; the campaign as it is now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CampaignDetail"}}}},"400":{"description":"Not allowed now, or out of bounds; `field` names it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldError"}}}},"401":{"description":"Signed out (no or expired session).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not this account's (or doesn't exist).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/account/campaigns/{id}/creatives":{"post":{"tags":["account"],"operationId":"accountCreativeAdd","summary":"Add an image (it waits for review)","description":"PNG, JPEG, WebP or AVIF up to 5 MB, at one of the campaign's placement sizes or twice it (stored as @2x). Serves after the team approves it.","security":[{"panelSession":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file","landing_url"],"properties":{"file":{"type":"string","format":"binary"},"landing_url":{"type":"string","format":"uri"}}}}}},"responses":{"201":{"description":"Added, in review.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PanelCreative"}}}},"400":{"description":"Wrong size or format, or a bad landing_url.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldError"}}}},"401":{"description":"Signed out (no or expired session).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not this account's (or doesn't exist).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Over 5 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldError"}}}}}}},"/v1/account/campaigns/{id}/creatives/{cid}":{"patch":{"tags":["account"],"operationId":"accountCreativeUpdate","summary":"Edit a creative's text or link (it goes back to review)","description":"Text creatives take title (1–60), body (≤ 140, empty removes it), cta (≤ 24) and landing_url; bars take title (the line, 1–70), cta (≤ 20, empty removes it) and landing_url; image creatives only landing_url. It stops serving until approved.","security":[{"panelSession":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"cid","in":"path","required":true,"schema":{"type":"integer"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"title":{"type":"string","minLength":1,"maxLength":70,"description":"≤ 60 for text."},"body":{"type":"string","maxLength":140,"description":"Text only."},"cta":{"type":"string","maxLength":24,"description":"≤ 20 for a bar."},"landing_url":{"type":"string","format":"uri"}}}}}},"responses":{"200":{"description":"Saved, in review.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PanelCreative"}}}},"400":{"description":"Bad field, or nothing to change.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldError"}}}},"401":{"description":"Signed out (no or expired session).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not this account's (or doesn't exist).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/account/ledger":{"get":{"tags":["account"],"operationId":"accountLedger","summary":"The balance statement, newest first","security":[{"panelSession":[]}],"parameters":[{"name":"before","in":"query","required":false,"schema":{"type":"integer"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"default":50}}],"responses":{"200":{"description":"A page of the statement.","content":{"application/json":{"schema":{"type":"object","required":["entries","next"],"properties":{"entries":{"type":"array","items":{"$ref":"#/components/schemas/LedgerEntry"}},"next":{"type":["integer","null"],"description":"Pass as `before` for older entries."}}}}}},"401":{"description":"Signed out (no or expired session).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/account/payments":{"get":{"tags":["account"],"operationId":"accountPayments","summary":"Pix payments, newest first","security":[{"panelSession":[]}],"responses":{"200":{"description":"Payments.","content":{"application/json":{"schema":{"type":"object","required":["payments"],"properties":{"payments":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"status":{"type":"string","enum":["pending","paid","expired","failed","refunded"]},"amount_cents":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"paid_at":{"type":["string","null"],"format":"date-time"}}}}}}}}},"401":{"description":"Signed out (no or expired session).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/account/top-ups":{"post":{"tags":["account"],"operationId":"accountTopUp","summary":"Add balance with Pix","description":"Opens a Pix charge at marx (R$ 50 to R$ 100.000). Poll `GET /v1/checkout/{token}` with the payment's token, and renew there. `document` (CPF or CNPJ) is needed when the account has none.","security":[{"panelSession":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["amount_cents"],"properties":{"amount_cents":{"type":"integer","minimum":5000,"maximum":10000000},"document":{"type":"string"}}},"example":{"amount_cents":20000}}}},"responses":{"201":{"description":"The Pix is open.","content":{"application/json":{"schema":{"type":"object","required":["payment"],"properties":{"payment":{"$ref":"#/components/schemas/Payment"}}}}}},"400":{"description":"Amount out of range, or a CPF/CNPJ is needed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FieldError"}}}},"401":{"description":"Signed out (no or expired session).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many top-ups; see Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"description":"marx couldn't open the charge (`status` failed); renew to retry.","content":{"application/json":{"schema":{"type":"object","required":["payment"],"properties":{"payment":{"$ref":"#/components/schemas/Payment"}}}}}},"503":{"description":"Payments are off.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/payments/marx":{"post":{"tags":["payments"],"operationId":"marxWebhook","summary":"marx's webhook (not for clients)","description":"Where marx reports charges. Signed with HMAC-SHA256 (`X-Marx-Signature: t=…,v1=…` over `<t>.<raw body>`, five minutes of tolerance); each `X-Marx-Event-Id` is handled once. A settled charge credits the account once; refunds are taken from the balance.","responses":{"204":{"description":"Handled (or already handled, or not about a teia payment)."},"401":{"description":"Missing, wrong or stale signature.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"Webhooks aren't configured (no MARX_WEBHOOK_SECRET).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/livez":{"get":{"tags":["meta"],"operationId":"livez","summary":"Liveness (no database check)","responses":{"200":{"description":"The process answers.","content":{"application/json":{"example":{"ok":true}}}}}}},"/openapi.json":{"get":{"tags":["meta"],"operationId":"openapi","summary":"This document","responses":{"200":{"description":"OpenAPI 3.1 JSON."}}}}},"components":{"securitySchemes":{"panelSession":{"type":"apiKey","in":"cookie","name":"teia_account"}},"parameters":{"Offer":{"name":"offer","in":"path","required":true,"description":"The offer token from a served item (`href` is `c/<token>`).","schema":{"type":"string","maxLength":512}}},"responses":{"BadRequest":{"description":"Invalid JSON or a field that fails validation (named in `error`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"TooLarge":{"description":"Body over 64 KB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Redirect":{"description":"Redirect (always, even when the click can't be recorded).","headers":{"Location":{"schema":{"type":"string","format":"uri"}},"Cache-Control":{"schema":{"type":"string","const":"no-store"}}}}},"schemas":{"Metrics":{"type":"object","required":["views","viewable","clicks","ctr","spend_cents"],"properties":{"views":{"type":"integer","description":"Rendered impressions without quality flags (what a CPM campaign pays for)."},"viewable":{"type":"integer"},"clicks":{"type":"integer","description":"Valid clicks only."},"ctr":{"type":"number","description":"clicks ÷ views, 0–1."},"spend_cents":{"type":"integer","description":"By the campaign's pricing; today's is a running estimate until booked."}}},"DayPoint":{"type":"object","required":["day","views","viewable","clicks","ctr","spend_cents"],"properties":{"day":{"type":"string","format":"date"},"views":{"type":"integer","description":"Rendered impressions without quality flags (what a CPM campaign pays for)."},"viewable":{"type":"integer"},"clicks":{"type":"integer","description":"Valid clicks only."},"ctr":{"type":"number","description":"clicks ÷ views, 0–1."},"spend_cents":{"type":"integer","description":"By the campaign's pricing; today's is a running estimate until booked."}}},"Account":{"type":"object","required":["id","email","name","company","phone","document_masked","created_at"],"properties":{"id":{"type":"integer"},"email":{"type":"string"},"name":{"type":"string"},"company":{"type":"string"},"phone":{"type":"string"},"document_masked":{"type":"string","example":"•••.•••.•47-25"},"created_at":{"type":"string","format":"date-time"}}},"CampaignSummary":{"type":"object","required":["id","name","status","pause_reason","starts_at","ends_at","daily_cents","pricing","cpm_cents","sites","placements","totals","in_review"],"properties":{"id":{"type":"integer"},"name":{"type":"string"},"status":{"type":"string","enum":["draft","live","paused","ended"],"description":"draft = in review."},"pause_reason":{"type":["string","null"],"enum":["advertiser","balance","admin",null]},"starts_at":{"type":["string","null"],"format":"date-time"},"ends_at":{"type":["string","null"],"format":"date-time","description":"Exclusive: midnight after the last day."},"daily_cents":{"type":["integer","null"],"description":"Per day: the daily rate. Per view: the most a day can cost."},"pricing":{"type":"string","enum":["day","cpm"],"description":"`day`: daily rate × hours with delivery ÷ 24. `cpm`: per 1,000 views."},"cpm_cents":{"type":["integer","null"],"description":"Price per 1,000 views (CPM only, else null)."},"sites":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"}}}},"placements":{"type":"array","items":{"type":"string"},"description":"site:placement; empty = all."},"totals":{"$ref":"#/components/schemas/Metrics"},"in_review":{"type":"integer","description":"Creatives waiting for review."}}},"PanelCreative":{"type":"object","required":["id","kind","status","totals"],"properties":{"id":{"type":"integer"},"kind":{"type":"string","enum":["image","text","bar"],"description":"`bar`: a text bar's line (`title`) and `cta`."},"status":{"type":"string","enum":["live","paused","review","rejected"]},"review_note":{"type":["string","null"],"description":"Why it was rejected."},"width":{"type":["integer","null"]},"height":{"type":["integer","null"]},"image_url":{"type":["string","null"]},"image2x_url":{"type":["string","null"]},"title":{"type":["string","null"]},"body":{"type":["string","null"]},"cta":{"type":["string","null"]},"landing_url":{"type":["string","null"]},"totals":{"$ref":"#/components/schemas/Metrics"}}},"CampaignDetail":{"allOf":[{"$ref":"#/components/schemas/CampaignSummary"},{"type":"object","required":["targeting","series","by_placement","creatives","can","spend_today_cents"],"properties":{"targeting":{"type":"object","properties":{"langs":{"type":"array","items":{"type":"string"}},"countries":{"type":"array","items":{"type":"string"}},"devices":{"type":"array","items":{"type":"string"}}}},"series":{"type":"array","items":{"$ref":"#/components/schemas/DayPoint"}},"by_placement":{"type":"array","items":{"allOf":[{"$ref":"#/components/schemas/Metrics"},{"type":"object","properties":{"site":{"type":"string"},"placement":{"type":"string"},"name":{"type":"string"}}}]}},"creatives":{"type":"array","items":{"$ref":"#/components/schemas/PanelCreative"}},"can":{"type":"object","properties":{"pause":{"type":"boolean"},"resume":{"type":"boolean"},"edit":{"type":"boolean"}}},"spend_today_cents":{"type":"integer","description":"So far, by the campaign's pricing; not booked yet."}}}]},"Overview":{"type":"object","required":["balance_cents","daily_cents_live","days_left","totals","series","campaigns","requests"],"properties":{"balance_cents":{"type":"integer"},"daily_cents_live":{"type":"integer","description":"Sum of live paid campaigns' daily rates."},"days_left":{"type":["integer","null"],"description":"Whole days the balance covers."},"totals":{"$ref":"#/components/schemas/Metrics"},"series":{"type":"array","items":{"$ref":"#/components/schemas/DayPoint"}},"campaigns":{"type":"array","items":{"$ref":"#/components/schemas/CampaignSummary"}},"requests":{"type":"array","items":{"type":"object","properties":{"ref":{"type":"string"},"title":{"type":"string"},"status":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"verified":{"type":"boolean"}}}}}},"LedgerEntry":{"type":"object","required":["id","kind","amount_cents","balance_after_cents","note","created_at"],"properties":{"id":{"type":"integer"},"kind":{"type":"string","enum":["top_up","campaign_spend","refund","adjustment","bonus"]},"amount_cents":{"type":"integer"},"balance_after_cents":{"type":"integer"},"note":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"campaign":{"type":["object","null"],"properties":{"id":{"type":"integer"},"name":{"type":"string"}}},"payment_code":{"type":["string","null"]},"day":{"type":["string","null"],"format":"date"}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"FieldError":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"field":{"type":"string","description":"Dotted path of the bad field."}}},"CheckoutPayload":{"description":"A campaign request (RequestPayload with kind `advertiser`, budget in BRL) plus the checkout fields.","allOf":[{"$ref":"#/components/schemas/RequestPayload"},{"type":"object","required":["billing","checkout_key"],"properties":{"billing":{"type":"object","required":["document"],"properties":{"document":{"type":"string","description":"CPF or CNPJ (letters allowed, as in CNPJs from July 2026); punctuation is ignored."}}},"checkout_key":{"type":"string","pattern":"^[A-Za-z0-9_-]{8,64}$","description":"Chosen by the client and reused on retries of the same checkout."}}}]},"Checkout":{"type":"object","required":["ref","payment"],"properties":{"ref":{"type":["string","null"],"description":"The request's reference (T-…)."},"payment":{"$ref":"#/components/schemas/Payment"}}},"Payment":{"type":"object","required":["code","token","status","amount_cents","expires_at"],"properties":{"code":{"type":"string","pattern":"^P-[0-9A-Z]{6}$","description":"Quote it to LOCO."},"token":{"type":"string","description":"For GET /v1/checkout/{token}."},"status":{"type":"string","enum":["pending","paid","expired","failed","refunded"]},"amount_cents":{"type":"integer","description":"Centavos (BRL)."},"pix_code":{"type":["string","null"],"description":"Pix copia e cola (draw the QR code from it); only while pending."},"payment_url":{"type":["string","null"],"description":"marx's payment page, for paying on another device; only while pending."},"paid_at":{"type":["string","null"],"format":"date-time"},"expires_at":{"type":"string","format":"date-time","description":"After this an unpaid payment is expired (a late Pix is still credited)."}}},"RequestCreated":{"type":"object","required":["ref"],"properties":{"ref":{"type":"string","pattern":"^T-[0-9A-Z]{6}$"}}},"RequestPayload":{"type":"object","required":["kind","consent","contact"],"properties":{"kind":{"type":"string","enum":["advertiser","publisher"]},"lang":{"type":"string","enum":["pt","en"]},"consent":{"type":"boolean","const":true,"description":"The person agreed that this is a request to be reviewed."},"contact":{"type":"object","required":["name","email"],"properties":{"name":{"type":"string","maxLength":120},"email":{"type":"string","format":"email","maxLength":200},"phone":{"type":"string","maxLength":40},"company":{"type":"string","maxLength":120}}},"notes":{"type":"string","maxLength":2000},"advertiser":{"type":"object","description":"Required when kind is advertiser.","required":["campaign","audience","creatives","budget"],"properties":{"campaign":{"type":"object","required":["name","brand","goal"],"properties":{"name":{"type":"string","maxLength":120},"brand":{"type":"string","maxLength":120},"website":{"type":"string","maxLength":500},"category":{"type":"string","maxLength":60},"goal":{"type":"string","enum":["traffic","awareness","launch"]}}},"audience":{"type":"object","required":["placements"],"properties":{"sites":{"type":"array","items":{"type":"string","enum":["tabdj","mundim"]}},"placements":{"type":"array","minItems":1,"maxItems":20,"items":{"type":"string","pattern":"^(tabdj|mundim):[a-z0-9-]{1,32}$"}},"langs":{"type":"array","items":{"type":"string","enum":["pt","en","es"]}},"countries":{"type":"array","maxItems":30,"items":{"type":"string","pattern":"^[A-Z]{2}$"}},"devices":{"type":"array","items":{"type":"string","enum":["desktop","mobile","tablet"]}}}},"creatives":{"type":"object","required":["url"],"properties":{"format":{"type":["string","null"],"enum":["image","text","both",null],"description":"Required unless every placement is a text bar (`tabdj:top`); then it may be left out."},"url":{"type":"string","description":"Where clicks go (http or https)."},"text":{"type":"object","description":"Required unless format is image.","properties":{"title":{"type":"string","maxLength":60},"body":{"type":"string","maxLength":140},"cta":{"type":"string","maxLength":24}}},"bar":{"type":"object","description":"The line of a text bar. Required when a bar placement (`tabdj:top`) is chosen; ignored otherwise.","required":["text"],"properties":{"text":{"type":"string","minLength":1,"maxLength":70},"cta":{"type":"string","maxLength":20}}},"sizes":{"type":"array","maxItems":8,"items":{"type":"string","pattern":"^\\d{2,4}x\\d{2,4}$"}}}},"budget":{"type":"object","required":["model","start","weeks","total","currency"],"properties":{"model":{"type":"string","enum":["week","cpm"],"description":"`week`: paid per day (the total over the weeks). `cpm`: paid per 1,000 views at teia's list price (any price sent is ignored), at most the total ÷ days a day."},"start":{"type":"string","format":"date"},"weeks":{"type":"integer","minimum":1,"maximum":52},"total":{"type":"number","minimum":1,"maximum":1000000},"currency":{"type":"string","enum":["BRL","USD"]}}}}},"publisher":{"type":"object","description":"Required when kind is publisher.","required":["property","spots","rules","payout"],"properties":{"property":{"type":"object","required":["kind","name","url","audience"],"properties":{"kind":{"type":"string","enum":["site","app","game"]},"name":{"type":"string","maxLength":120},"url":{"type":"string","maxLength":500},"category":{"type":"string","maxLength":60},"langs":{"type":"array","items":{"type":"string","enum":["pt","en","es"]}},"countries":{"type":"array","items":{"type":"string","pattern":"^[A-Z]{2}$"}},"audience":{"type":"string","enum":["lt10k","10k-50k","50k-200k","200k-1m","gt1m","unknown"]}}},"spots":{"type":"array","minItems":1,"maxItems":12,"items":{"type":"object","required":["name","size","where"],"properties":{"name":{"type":"string","maxLength":60},"size":{"type":"string","description":"`WxH`, `native` or `bar` (a text bar)."},"where":{"type":"string","enum":["top","side","inline","bottom","screen","other"]},"devices":{"type":"array","items":{"type":"string","enum":["desktop","mobile","tablet"]}}}}},"rules":{"type":"object","required":["approval","empty"],"properties":{"blocked":{"type":"array","maxItems":20,"items":{"type":"string"}},"approval":{"type":"string","enum":["each","auto"]},"empty":{"type":"string","enum":["advertise","collapse"]}}},"payout":{"type":"object","required":["method","entity"],"properties":{"method":{"type":"string","enum":["pix","transfer","paypal","other"]},"entity":{"type":"string","enum":["person","company"]}}}}}}},"Key":{"type":"string","pattern":"^[a-z0-9][a-z0-9_-]{0,63}$"},"Breakpoint":{"type":"string","enum":["mobile","tablet","desktop"]},"ServeRequest":{"type":"object","required":["site","pv","slots"],"properties":{"site":{"$ref":"#/components/schemas/Key","description":"Site key (`tabdj`)."},"pv":{"type":"string","maxLength":64,"description":"Page view id: random per page load, never stored in the browser. Groups the slots of one page."},"slots":{"type":"array","minItems":1,"maxItems":12,"description":"Up to 12; extra slots are ignored.","items":{"$ref":"#/components/schemas/SlotRequest"}},"page":{"$ref":"#/components/schemas/PageInfo"},"vp":{"type":"object","description":"Viewport size in CSS pixels.","required":["w","h"],"properties":{"w":{"type":"number"},"h":{"type":"number"}}},"ctx":{"type":"object","maxProperties":20,"description":"App key-values used by targeting (`kv`) and stored with the decision, e.g. `{ place: \"beach\" }`. Up to 20 keys following the key pattern (more are ignored). Strings are stripped of control characters, trimmed and cut at 100 characters, and dropped when empty; other types are dropped.","additionalProperties":{"type":["string","number","boolean"],"maxLength":100}}}},"SlotRequest":{"type":"object","required":["placement"],"properties":{"id":{"type":"string","maxLength":32,"description":"Echoed back; defaults to the index."},"placement":{"$ref":"#/components/schemas/Key"},"bp":{"$ref":"#/components/schemas/Breakpoint","description":"Picks the slot's size. Without it, the breakpoint of `vp.w` when the request has `vp` (what the tag does). Apps should send it."},"y":{"type":"number","description":"Slot top in document pixels."},"above":{"type":"boolean","description":"Starts in the first viewport."}}},"PageInfo":{"type":"object","description":"Page context, logged for analysis and contextual matching. All optional.","properties":{"url":{"type":"string","maxLength":2000,"description":"Canonical URL, query string removed except utm_*. Its path picks the page group."},"lang":{"type":"string","description":"`<html lang>`; picks the disclosure language."},"title":{"type":"string","maxLength":300},"desc":{"type":"string","maxLength":300,"description":"Meta description."},"og":{"type":"string","description":"og:type"},"ld":{"type":"array","maxItems":10,"items":{"type":"string"},"description":"JSON-LD @type values."},"ref":{"type":"string","description":"Referrer host only."},"utm":{"type":"object","properties":{"source":{"type":"string"},"medium":{"type":"string"},"campaign":{"type":"string"},"term":{"type":"string"},"content":{"type":"string"}}},"clickIds":{"type":"array","maxItems":8,"items":{"type":"string"},"description":"Names of ad click ids present in the URL (gclid, fbclid…), never their values."}}},"ServeResponse":{"type":"object","required":["pv","slots"],"properties":{"pv":{"type":"string"},"slots":{"type":"array","items":{"$ref":"#/components/schemas/SlotResponse"}}}},"SlotResponse":{"type":"object","required":["id","placement","items","fallback","disclosure"],"properties":{"id":{"type":"string"},"placement":{"type":"string"},"items":{"type":"array","items":{"$ref":"#/components/schemas/ServedItem"}},"fallback":{"type":["string","null"],"enum":["advertise","collapse",null],"description":"Set when nothing was eligible. \"advertise\": show an \"advertise here\" card linking to `advertise.href`; \"collapse\": hide the slot (never above the fold)."},"disclosure":{"type":"string","description":"\"Publicidade\" / \"Links de afiliado\" in the page language. Always show it."},"advertise":{"type":"object","description":"The \"advertise here\" link; absent when the placement turns it off.","required":["label","href"],"properties":{"label":{"type":"string"},"href":{"type":"string"}}},"size":{"type":"object","description":"The slot's reserved size for the requested breakpoint. For a bar, `h` is the bar's height (disclosure included) and `w` the widest its content gets.","properties":{"w":{"type":"number"},"h":{"type":"number"}}},"bar":{"type":"boolean","const":true,"description":"The placement is a text bar: draw one strip `size.h` high, with the disclosure and the advertise link inside it, and each item as its `title` and `cta`. Absent otherwise."}}},"ServedItem":{"type":"object","required":["offer","kind","href","rel"],"properties":{"offer":{"type":"string","description":"Signed offer token; send it back in events."},"kind":{"type":"string","enum":["image","text","affiliate_card","bar"],"description":"In a bar slot, `bar` or `text` items carry only `title` and `cta`."},"w":{"type":"number"},"h":{"type":"number"},"img":{"type":"string","format":"uri"},"img2x":{"type":"string","format":"uri"},"pixel":{"type":"string","format":"uri","description":"Pixel-art version (PNG) for pixel surfaces. On a pixel placement every item has it instead of `img`/`img2x`, and `w`/`h` are the placement's size, which it is made at."},"title":{"type":"string","description":"Card title, a bar's line of text, or alt text for images."},"body":{"type":"string"},"cta":{"type":"string"},"href":{"type":"string","description":"Click URL, relative to the request base (`c/<token>`)."},"rel":{"type":"string","const":"sponsored noopener"}}},"EventBatch":{"type":"object","required":["e"],"properties":{"e":{"type":"array","maxItems":50,"items":{"$ref":"#/components/schemas/EventIn"}}}},"EventIn":{"type":"object","required":["o","t"],"properties":{"o":{"type":"string","description":"Offer token."},"t":{"type":"string","enum":["render","viewable","click","advertise_click","error"]},"ttv":{"type":"number","description":"ms from render to viewable."},"inview":{"type":"number","description":"ms in view."},"depth":{"type":"number","minimum":0,"maximum":1,"description":"Scroll depth when viewable."},"err":{"type":"string","description":"Error kind, e.g. \"img\"."}}}}}}