# Knobase Ads developer contract (V1) Knobase Ads is a UI-neutral, policy-aware sponsored-offer decision and tracked-link service. It does not modify the publisher's organic AI answer. It never requires a compact card, post-answer card, panel, sidebar, empty slot, or a new chat UI. Publishers may use an eligible offer only in an existing, separate, clickable native surface. Every rendered offer must visibly show sponsorship disclosure, advertiser identity, and the Knobase tracked link. If no compliant existing surface exists, the correct outcome is no-render. Do not append a raw URL or commercial copy to the organic answer. No-fill is a valid successful result. Keep the organic answer on no-fill, timeout, or error. This file is the canonical LLM contract. Copy for LLM copies this exact document. Source: https://knobase.com/docs/llms.txt OpenAPI: https://knobase.com/docs/openapi.json ## Authentication Authorization: Bearer $KNOBASE_API_KEY Content-Type: application/json - KNOBASE_API_KEY is server-side only, long-lived until revoked or rotated, and scoped to one app and environment. - Do not commit, expose, log, paste into browser code, or put the key in an LLM prompt. - Setup/bootstrap credentials (kb_setup_…) cannot call serving endpoints. - The app is inferred from the key. Do not send app_id. ## Implemented public endpoints - POST /v1/offers/decide — only V1 offer decision endpoint. Bearer required. 60 requests/minute/IP. - GET /r/{token} — public browser redirect for the returned tracked link. No Bearer. - POST /v1/events/rendered — record offer_rendered. Bearer + Idempotency-Key header. - POST /v1/events/qualified-impression — record qualified_impression. Bearer + Idempotency-Key header. - POST /v1/events/impression — alias for the two events above until first-party clients migrate. Still accepts body idempotency_key. - POST /v1/events/conversion — signed conversion. Bearer required. V1 returns billable:false, ledger_written:false. - GET /v1/decisions/{id} — optional decision metadata for the same app. Bearer required. Retired: POST /v1/offers:query returns 410 Gone. Successor is POST /v1/offers/decide. Not implemented: a public find_sponsored_offer MCP tool or a required UI renderer. ## Privacy and forbidden fields - message is the current user turn only (max 4000). Transient. Not persisted. No raw full transcript. - Optional typed session.digest has no free text. - No personal identity, email, phone, advertising ID, precise location, health, financial, biometric, or cross-app identity. - Rejected keys: email, phone, tel, user_id, userid, userId, name, full_name, fullname, lat, lng, latitude, longitude, address, ip, conversation, conversation_history, messages, raw_message, app_id, placement, display_style, surface_id, capabilities. - Serving requires audience.age_band=18_plus and audience.contextual_offer_consent=true. - native_surface.clickable and native_surface.disclosure_capable are required self-attestation booleans. Either false is CAPABILITY_UNMET. This is not production trust. - Unknown consent, age, or safety is safe no-fill (POLICY_BLOCK). - Live locales: ko* and en*. ## POST /v1/offers/decide Example request (sandbox / example data): { "message": "시니어 반려견에게 줄 부드러운 사료를 찾고 있어요", "locale": "ko", "country": "KR", "audience": { "age_band": "18_plus", "contextual_offer_consent": true }, "native_surface": { "clickable": true, "disclosure_capable": true } } Served response (HTTP 200): { "serve": true, "request_id": "req_01", "decision_id": "dec_01", "offer_id": "snp_01EXAMPLEOFFER", "expires_at": "2026-09-10T12:30:00.000Z", "disclosure": "광고", "advertiser": "Example Pet", "link": "https://knobase.com/r/tok_example", "title": "시니어 반려견을 위한 부드러운 식사 옵션", "description": "제품 정보와 원재료를 확인해보세요.", "cta": "제품 보기", "reason_codes": [ "MATCHED" ] } No-fill response (HTTP 200, success): { "serve": false, "request_id": "req_02", "decision_id": "dec_02", "reason_codes": [ "NO_ELIGIBLE_OFFER" ], "retry_after_seconds": 0 } - disclosure, advertiser, and link are required commercial-integrity fields. - title, description, image_url, and cta are optional presentation hints, not UI instructions. - offer_id is the snapshot id. Send it as offer_snapshot_id on events. - Handler budget is 400ms. Timeout fail-opens as serve:false with TIMEOUT. Reason codes: - POLICY_BLOCK: Audience, safety, or policy did not allow a serve. - LOW_INTENT: Intent was too weak to serve. - NO_ELIGIBLE_OFFER: No matching eligible offer. - BUDGET_EXHAUSTED: Campaign or account budget is exhausted. - TIMEOUT: Handler budget exceeded. Fail open. - LOCALE_NOT_LIVE: Locale is not in the live serving set. - FREQUENCY_CAPPED: Session frequency cap reached. - MATCHED: An eligible offer was returned. - INVALID_ENVELOPE: Request failed schema or unknown-field validation. - FORBIDDEN_IDENTITY_FIELD: A forbidden identity or placement field was present. - CAPABILITY_UNMET: native_surface.clickable or disclosure_capable was false. Auth/validation errors use HTTP 401/400/429 with { ok: false, error }. Treat those as no-render. ## Redirect Use the returned Knobase link. Never replace it with a raw merchant URL. GET /r/{token} is a browser redirect. Invalid tokens do not become arbitrary destinations. An optional browser integrity challenge may appear; it is not a publisher integration API. ## Events Rendered: { "offer_snapshot_id": "snp_01EXAMPLEOFFER", "decision_id": "dec_01", "type": "offer_rendered", "idempotency_key": "idemp_rendered_01" } Qualified impression (same path): { "offer_snapshot_id": "snp_01EXAMPLEOFFER", "decision_id": "dec_01", "type": "qualified_impression", "idempotency_key": "idemp_qualified_01" } Conversion: { "offer_snapshot_id": "snp_01EXAMPLEOFFER", "decision_id": "dec_01", "timestamp": 1757491200000, "nonce": "nonce_example_01", "signature": "aaaaaaaaaaaaaaaa" } Conversion is a signed server postback only. Timestamp must be within ±10 minutes. Do not invent a signing secret. Do not send conversions from the browser. ## UI non-interference Knobase does not change your product UI or organic AI answer. It does not create cards, panels, empty slots, or new chat components. Render an offer only in an existing separate native surface that can clearly display sponsorship disclosure, advertiser identity, and the tracked Knobase link. If no such surface exists, do not render the offer. ## Safe server-side integration 1. Generate the organic answer normally. 2. Independently call POST /v1/offers/decide on the server. 3. If serve:false or the request fails, render nothing extra. 4. If serve:true, the host may use its existing separate clickable surface. 5. Keep the offer separate from the organic answer. 6. Use the returned Knobase link, not the raw merchant URL. 7. Do not put offer content into a model system prompt or ask the model to write sponsored text. 8. Never reserve blank UI while waiting. Fail open. ## Implementation checklist - [ ] KNOBASE_API_KEY read from server env only - [ ] Organic answer generated independently - [ ] Decide called with current-turn context only - [ ] Forbidden identity and placement fields omitted - [ ] serve:false / timeout / error => no-render - [ ] serve:true used only on an existing clickable surface that can show disclosure, advertiser, and link - [ ] No new card, panel, slot, or chat UI - [ ] Knobase link used for clicks - [ ] Events sent only after a real surface used the offer