Partner API
    New

    Complete integration guide for the Brand Kit OS Partner API — authentication, endpoints, data model, webhooks, and Leafpad mapping.

    Last updated: February 19, 2026

    Partner API Integration Guide

    The Brand Kit OS Partner API lets partner applications pull rich brand context to enhance AI-generated content. This guide covers everything you need to integrate.

    Base URL:

    https://fupwpcqmyykfiuakjxxc.supabase.co/functions/v1/partner-api
    

    Getting Started

    Integration takes three steps:

    1. Link — Call POST /v1/integrations/partner/link with a brand_kit_id to receive your API key and webhook secret.
    2. Pull — Call GET /v1/brand-kits/{id} before generating content to fetch the latest brand context.
    3. Listen — Receive webhook pushes when a brand kit is updated or deleted.

    Authentication

    Every request requires a Bearer token in the Authorization header:

    Authorization: Bearer pk_a1b2c3d4e5f6...
    

    Per-Partner Keys (recommended)

    During the /link step, Brand Kit OS returns a unique API key prefixed with pk_. This key is:

    • Shown only once — save it securely
    • Hashed (SHA-256) before storage — we never store the plaintext
    • Independently revocable per partner integration
    • Used for all subsequent API calls

    Global Key (deprecated)

    The legacy PARTNER_API_KEY environment variable is still accepted as a fallback. It will be removed in a future version. Migrate to per-partner keys.


    API Reference

    POST /v1/integrations/partner/link

    Links a partner app to a Brand Kit OS brand kit. Called once during onboarding.

    Request:

    curl -X POST \
      https://fupwpcqmyykfiuakjxxc.supabase.co/functions/v1/partner-api/v1/integrations/partner/link \
      -H "Authorization: Bearer <GLOBAL_PARTNER_KEY>" \
      -H "Content-Type: application/json" \
      -d '{"brand_kit_id": "uuid-here", "external_user_id": "leafpad-user-123", "external_workspace_id": "leafpad-ws-456", "webhook_url": "https://api.leafpad.app/webhooks/bkos"}'
    
    Field Type Required Description
    brand_kit_id UUID The Brand Kit OS brand kit to link
    external_user_id string No Your user's ID in your system
    external_workspace_id string No Your workspace/org ID
    webhook_url string (HTTPS) No URL to receive webhook deliveries

    Response (201):

    {
      "integration": {
        "id": "uuid",
        "brand_kit_id": "uuid",
        "partner_name": "leafpad",
        "external_user_id": "leafpad-user-123",
        "external_workspace_id": "leafpad-ws-456",
        "webhook_url": "https://api.leafpad.app/webhooks/bkos",
        "is_active": true
      },
      "partner_api_key": "pk_a1b2c3d4e5f67890...",
      "webhook_secret": "uuid-uuid",
      "message": "Save these credentials — they will not be shown again."
    }
    

    ⚠️ Save partner_api_key and webhook_secret immediately. They are displayed only once.


    GET /v1/brand-kits/{brand_kit_id}

    Returns the full brand kit data for a single brand kit.

    curl https://fupwpcqmyykfiuakjxxc.supabase.co/functions/v1/partner-api/v1/brand-kits/<BRAND_KIT_ID> \
      -H "Authorization: Bearer pk_a1b2c3d4..."
    

    Response (200):

    {
      "brand_kit_id": "uuid",
      "workspace_id": "uuid",
      "updated_at": "2026-02-19T12:00:00Z",
      "version": 3,
      "compat": { "..." },
      "full": { "..." }
    }
    

    See Data Model Reference for the compat and full schemas.


    GET /v1/brand-kits

    Lists brand kits linked to a partner identity.

    curl "https://fupwpcqmyykfiuakjxxc.supabase.co/functions/v1/partner-api/v1/brand-kits?external_user_id=leafpad-user-123&limit=20&offset=0" \
      -H "Authorization: Bearer pk_a1b2c3d4..."
    
    Parameter Type Default Description
    external_user_id string Filter by your user ID
    external_workspace_id string Filter by your workspace ID
    limit integer 20 Max results (1–100)
    offset integer 0 Pagination offset

    At least one of external_user_id or external_workspace_id is required.

    Response (200):

    {
      "data": [ { "brand_kit_id": "...", "compat": {}, "full": {} } ],
      "pagination": { "limit": 20, "offset": 0, "count": 1 }
    }
    

    Data Model Reference

    Every brand kit response contains two data layers:

    compat — Drop-in Fields

    Simplified fields that map directly to common partner fields (Description, Keywords, Style).

    Field Type Description
    brand_description string Combined business description and brand story
    writing_style string Comma-separated tone descriptors, sentence style, vocabulary level
    seo_keywords string[] SEO keywords derived from products and benefits
    brand_keywords string[] Brand identity keywords from personality traits and values
    image_style string Visual/image style description

    full — Rich Context

    Detailed brand data organized into 8 categories.

    full.voice

    Field Type Description
    tone string[] Tone descriptors (e.g. "professional", "warm")
    personality_traits string[] Brand personality trait names
    do_phrases string[] Encouraged phrases and language patterns
    dont_phrases string[] Phrases to avoid
    reading_level string Target vocabulary level
    formality string Formality level (e.g. "casual", "formal")
    humor_level string How much humor to use
    verbal_style object Full verbal style settings (sentence style, vocabulary, etc.)
    voice_archetypes array Selected voice archetype definitions
    tone_dimensions object Dimensional tone settings (e.g. enthusiasm, assertiveness)

    full.audience

    Field Type Description
    personas array Target audience personas, each with: name, title, type, is_primary, demographics, goals, pain_points, values, channels
    target_roles string[] Job titles / roles of target audience
    pain_points string[] All audience pain points (flattened)
    objections string[] Common sales objections / barriers

    full.messaging

    Field Type Description
    value_props string[] Brand promises and value propositions
    positioning string Brand positioning / mission statement
    differentiators string[] Competitive differentiation points from products
    elevator_pitch string Brand story as elevator pitch
    tagline_options string[] Brand taglines

    full.visuals

    Field Type Description
    image_style string Visual style description
    color_palette array Colors: [{ "name": "primary", "value": "#hex" }]
    typography object { "heading": "Font", "body": "Font", "paragraph": "Font" }
    visual_do string[] Visual guidelines to follow
    visual_dont string[] Visual elements to avoid

    full.seo

    Field Type Description
    primary_keywords string[] Primary SEO keywords
    secondary_keywords string[] Secondary keywords
    internal_linking_rules string Linking strategy
    geo_focus string Geographic focus
    competitor_keywords string[] Competitor keyword targets

    full.constraints

    Field Type Description
    taboo_topics string[] Topics to never mention
    compliance_notes string Compliance/legal notes
    claims_to_avoid string[] Claims that should not be made
    required_disclaimers string[] Required disclosure text

    full.examples

    Field Type Description
    good_examples array Positive examples: [{ "platform": "blog", "content": "...", "original": "..." }]
    bad_examples array Negative examples with same structure
    reference_urls string[] Brand website URL(s)

    full.ctas

    Field Type Description
    primary_ctas string[] Primary calls-to-action (USPs from products)
    secondary_ctas string[] Secondary CTAs (key benefits from products)
    offer_details string Special pricing/offer information

    Webhooks

    When a brand kit is updated or deleted, Brand Kit OS sends a POST request to your registered webhook_url.

    Events

    Event Trigger
    brand_kit.upserted Brand kit created or updated
    brand_kit.deleted Brand kit deleted

    Payload

    {
      "event_id": "uuid",
      "event_type": "brand_kit.upserted",
      "occurred_at": "2026-02-19T12:00:00Z",
      "brand_kit_id": "uuid",
      "workspace_id": "uuid",
      "version": 3,
      "compat": { "..." },
      "full": { "..." }
    }
    

    For brand_kit.deleted, the compat and full fields are omitted.

    Signature Verification

    Every webhook includes two headers for verification:

    Header Description
    X-Timestamp Unix epoch seconds when the payload was signed
    X-Signature hex(HMAC_SHA256(timestamp + "." + body, webhook_secret))

    Reject the webhook if the timestamp is more than 5 minutes old (replay protection).

    Node.js Example

    const crypto = require("crypto");
    
    function verifyWebhook(body, secret, timestamp, signature) {
      const now = Math.floor(Date.now() / 1000);
      if (Math.abs(now - timestamp) > 300) return false; // 5 min window
    
      const expected = crypto
        .createHmac("sha256", secret)
        .update(`${timestamp}.${body}`)
        .digest("hex");
    
      return crypto.timingSafeEqual(
        Buffer.from(expected),
        Buffer.from(signature)
      );
    }
    

    Python Example

    import hmac, hashlib, time
    
    def verify_webhook(body: str, secret: str, timestamp: int, signature: str) -> bool:
        if abs(time.time() - timestamp) > 300:
            return False
    
        expected = hmac.new(
            secret.encode(),
            f"{timestamp}.{body}".encode(),
            hashlib.sha256
        ).hexdigest()
    
        return hmac.compare_digest(expected, signature)
    

    Error Handling

    All errors return a consistent JSON structure:

    {
      "error": {
        "code": "ERROR_CODE",
        "message": "Human-readable message",
        "details": {}
      }
    }
    
    HTTP Status Code Meaning
    400 BAD_REQUEST Invalid or missing parameters
    400 INVALID_ID UUID format is incorrect
    401 UNAUTHORIZED Invalid or missing API key
    404 NOT_FOUND Brand kit or endpoint not found
    429 RATE_LIMITED Too many requests
    500 SERVER_ERROR Internal error — retry with backoff

    Retry Guidance

    • 429: Wait and retry after 60 seconds
    • 500: Retry with exponential backoff (1s, 2s, 4s, max 3 retries)
    • 4xx (other): Do not retry — fix the request

    Rate Limits

    Limit Value
    Requests per minute 60
    Applies to All endpoints equally

    When rate limited, you receive a 429 response. Wait for the current window to expire (≤60 seconds) before retrying.


    Leafpad Mapping Guide

    This section maps Brand Kit OS data to Leafpad's current fields for easy integration.

    Direct Field Mapping (compat layer)

    Leafpad Field Brand Kit OS Field Notes
    Business Description compat.brand_description Combined description + brand story
    Keywords compat.seo_keywords + compat.brand_keywords Merge both arrays for comprehensive keywords
    Verbal Style compat.writing_style Comma-separated style descriptors
    Visual Style compat.image_style Image/visual style description
    Website URL full.examples.reference_urls[0] First reference URL

    Enhanced AI Context (full layer)

    For superior blog content, feed these additional fields into your AI prompt:

    Category What to Include Impact on Content
    Voice full.voice.tone, full.voice.do_phrases, full.voice.dont_phrases Matches brand tone exactly
    Audience full.audience.personas[0].pain_points, full.audience.personas[0].goals Targets the right reader
    Messaging full.messaging.value_props, full.messaging.differentiators Stays on brand message
    Constraints full.constraints.taboo_topics, full.constraints.claims_to_avoid Avoids brand violations
    Examples full.examples.good_examples Shows the AI what "good" looks like
    CTAs full.ctas.primary_ctas Drives conversions

    Example AI Prompt Enhancement

    You are writing a blog post for [brand].
    
    TONE: Use these descriptors: ${compat.writing_style}
    DO SAY: ${full.voice.do_phrases.join(", ")}
    DON'T SAY: ${full.voice.dont_phrases.join(", ")}
    AUDIENCE: Writing for ${full.audience.personas[0].name} who struggles with ${full.audience.pain_points.join(", ")}
    NEVER MENTION: ${full.constraints.taboo_topics.join(", ")}
    INCLUDE CTA: ${full.ctas.primary_ctas[0]}
    

    API Calls Summary

    Direction Endpoint When Purpose
    Partner → BKOS POST /v1/integrations/partner/link Once during setup Exchange credentials
    Partner → BKOS GET /v1/brand-kits/{id} Before generating content Pull latest brand context
    Partner → BKOS GET /v1/brand-kits?external_user_id=X On dashboard load List linked brand kits
    BKOS → Partner Webhook brand_kit.upserted When brand kit updates Push fresh data
    BKOS → Partner Webhook brand_kit.deleted When brand kit deleted Clean up cached data