← Back to Blog
guides14 min read

How to Score Ad Creatives with AI: The Kettio Rank API Guide

Spencer Merrill|
How to Score Ad Creatives with AI: The Kettio Rank API Guide
API GuideHow to Score Ad Creatives with AI: The Kettio Rank API Guide

The Kettio Rank API lets you submit ad creatives and get them scored and ranked by predicted performance against a target audience. No media spend. No survey panels. No waiting days for results. You send images, you get scores — in seconds.

This guide walks through everything you need to know to integrate creative scoring into your marketing agent, ad platform, or internal tooling.

How AI Creative Scoring Works

Most attempts at AI ad scoring fail because they ask an LLM to rate an ad on a numerical scale. LLMs are terrible at this — they regress to the mean, produce inconsistent scores, and can't differentiate between a mediocre ad and a great one.

Kettio's SSR (Semantic Similarity Rating) pipeline takes a fundamentally different approach:

  1. Persona construction. Your target audience demographics are used to build a detailed synthetic persona — a rich behavioral profile that specifies how this person browses, what triggers their skepticism, how price-sensitive they are, and what kind of visual messaging resonates with them.
  2. Ensemble evaluation. Multiple AI models (Gemini Flash and Claude Haiku) independently evaluate each creative from the persona's perspective. Each model writes a natural-language rationale explaining what works and what doesn't. Using multiple models prevents any single model's biases from dominating.
  3. Semantic embedding. The rationales are embedded using OpenAI's text-embedding-3-small and compared against calibrated anchor texts that represent different quality levels. This converts subjective language ("this ad feels premium") into a numerical score that's comparable across evaluations.
  4. Score aggregation. The embedding similarities are aggregated into a final score (1-5 scale) with confidence metrics. Assets are ranked from best to worst.

The result: scores that correlate with real human preferences at ρ = 0.58+ on academic benchmarks, beating GPT-4o's zero-shot performance.

Getting Started

Step 1: Get Your API Key

Sign up at kettio.com/login and navigate to your dashboard. Generate an API key — it will look like agk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. Store it securely. The raw key is shown only once.

Step 2: Your First Rank Call

Here's the simplest possible scoring call — two ads, one audience, ranked by purchase intent:

curl -X POST https://kettio.com/api/v1/rank   -H "Authorization: Bearer agk_live_YOUR_KEY"   -H "Content-Type: application/json"   -d '{
    "assets": [
      { "url": "https://your-cdn.com/ad-variant-a.png", "id": "variant-a" },
      { "url": "https://your-cdn.com/ad-variant-b.png", "id": "variant-b" }
    ],
    "audience": {
      "name": "Millennial pet owners",
      "description": "Dog owners aged 25-34, mid-income, active on Instagram",
      "demographics": {
        "ageRange": "25-34",
        "incomeLevel": "50k-75k",
        "priceSensitivity": "moderate",
        "shoppingIntent": "researching",
        "platformFatigue": "high"
      }
    },
    "goal": "purchase-intent"
  }'

Step 3: Understand the Response

The API returns assets ranked from best to worst:

{
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "ranked": [
    {
      "rank": 1,
      "asset_url": "https://your-cdn.com/ad-variant-a.png",
      "asset_id": "variant-a",
      "score": 4.23,
      "rationale": "This ad immediately communicates value with a clear product shot and a relatable lifestyle context. The warm color palette and casual dog-owner imagery would resonate with millennial pet owners who are browsing Instagram. The price point is visible but not aggressive, which works well for a researching audience with moderate price sensitivity.",
      "product_read": "A promotional image showing a person with a golden retriever, featuring a subscription box for dog treats. Price shown as $29/month with a 'First box free' callout.",
      "confidence": "high",
      "confidence_details": {
        "entropy": 0.38,
        "top_margin": 0.35,
        "sample_std_dev": 0.06
      }
    },
    {
      "rank": 2,
      "asset_url": "https://your-cdn.com/ad-variant-b.png",
      "asset_id": "variant-b",
      "score": 2.87,
      "rationale": "The ad relies heavily on text-based value propositions without showing the actual product. For an audience with high platform fatigue, this format feels too similar to generic DTC ads they scroll past daily. The stock photography undermines authenticity.",
      "product_read": "A text-heavy banner ad for a dog treat subscription with stock photography of a generic dog. Lists three bullet points of benefits.",
      "confidence": "high",
      "confidence_details": {
        "entropy": 0.45,
        "top_margin": 0.28,
        "sample_std_dev": 0.09
      }
    }
  ],
  "errors": [],
  "summary": {
    "goal": "purchase-intent",
    "asset_type": "Social Media Post",
    "audience": "Millennial pet owners",
    "assets_ranked": 2,
    "assets_failed": 0,
    "credits_used": 2,
    "credits_remaining": 48
  }
}

Key fields to pay attention to:

  • score — 1 to 5 scale. 4+ is strong. Below 3 needs work.
  • rationale — Natural language explanation of why the score is what it is. This is written from the persona's perspective, which means it reflects how your target audience would actually react.
  • product_read — What the AI saw in your ad. Useful for verifying the model understood your creative correctly.
  • confidencehigh, medium, or low. If confidence is low, consider running a Champion-Challenger test for a more rigorous comparison.

JavaScript / TypeScript Integration

Here's a typed function you can drop into your agent or application:

interface RankRequest {
  assets: Array<{ url: string; id?: string }>;
  audience?: {
    name: string;
    description?: string;
    demographics?: Record<string, string>;
  };
  audience_id?: string;
  goal?: string;
  asset_type?: string;
}

interface RankedAsset {
  rank: number;
  asset_url: string;
  asset_id: string;
  score: number;
  rationale: string;
  product_read: string;
  confidence: 'high' | 'medium' | 'low';
  confidence_details: {
    entropy: number;
    top_margin: number;
    sample_std_dev: number;
  };
}

interface RankResponse {
  request_id: string;
  ranked: RankedAsset[];
  errors: Array<{ asset_url: string; error: string }>;
  summary: {
    goal: string;
    asset_type: string;
    audience: string;
    assets_ranked: number;
    assets_failed: number;
    credits_used: number;
    credits_remaining: number;
  };
}

async function rankCreatives(
  apiKey: string,
  request: RankRequest
): Promise<RankResponse> {
  const response = await fetch('https://kettio.com/api/v1/rank', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(request),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Rank API error: ${error.error}`);
  }

  return response.json();
}

Python Integration

import requests

def rank_creatives(api_key: str, assets: list, audience: dict,
                   goal: str = "purchase-intent") -> dict:
    response = requests.post(
        "https://kettio.com/api/v1/rank",
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        },
        json={
            "assets": assets,
            "audience": audience,
            "goal": goal
        }
    )
    response.raise_for_status()
    return response.json()


# Usage
result = rank_creatives(
    api_key="agk_live_YOUR_KEY",
    assets=[
        {"url": "https://cdn.example.com/ad-a.png", "id": "ad-a"},
        {"url": "https://cdn.example.com/ad-b.png", "id": "ad-b"},
    ],
    audience={
        "name": "Budget-conscious parents",
        "demographics": {
            "ageRange": "35-44",
            "priceSensitivity": "high",
            "shoppingIntent": "comparing"
        }
    },
    goal="purchase-intent"
)

for asset in result["ranked"]:
    print(f"#{asset['rank']}: {asset['asset_id']} — {asset['score']:.2f}")
    print(f"  {asset['rationale'][:120]}...")

Request Parameters Deep Dive

Assets

You can submit 1 to 20 image URLs per request. Each URL must be publicly accessible (the API fetches the image). The optional id field lets you correlate results back to your internal asset IDs.

{
  "assets": [
    { "url": "https://cdn.example.com/hero-shot.png", "id": "hero-v3" },
    { "url": "https://cdn.example.com/lifestyle.jpg", "id": "lifestyle-v1" },
    { "url": "https://cdn.example.com/ugc-style.png", "id": "ugc-v2" }
  ]
}

Rate limit: 60 assets per minute per API key. Each successfully ranked asset costs 1 credit.

Audience

You can provide an audience inline or reference a saved audience by ID:

// Inline audience
{
  "audience": {
    "name": "Gen Z sneaker enthusiasts",
    "description": "18-24 year olds who follow sneaker culture and drop dates",
    "demographics": {
      "ageRange": "18-24",
      "incomeLevel": "25k-50k",
      "adSkepticism": "high",
      "categoryFamiliarity": "expert",
      "shoppingIntent": "ready-to-buy",
      "platformFatigue": "very-high",
      "brandFamiliarity": "familiar"
    }
  }
}

// Saved audience
{
  "audience_id": "aud_abc123"
}

The more demographic fields you provide, the more specific the evaluation persona becomes — and the more accurate the scores. At minimum, provide name. For best results, include ageRange, priceSensitivity, shoppingIntent, and platformFatigue.

Demographics Reference

Field Values
ageRange 18-24, 25-34, 35-44, 45-54, 55-64, 65+, all-ages
incomeLevel under-25k, 25k-50k, 50k-75k, 75k-100k, 100k-150k, 150k+
adSkepticism very-low, low, moderate, high, very-high
trustBaseline very-low, low, moderate, high, very-high
priceSensitivity very-low, low, moderate, high, very-high
categoryFamiliarity none, casual, knowledgeable, expert
shoppingIntent browsing, researching, comparing, ready-to-buy
urgency none, low, moderate, high, urgent
brandFamiliarity unaware, heard-of, familiar, loyal
platformFatigue low, medium, high, very-high

Evaluation Goals

The goal parameter changes how the synthetic audience evaluates your creative. Default is purchase-intent. See the complete goals reference for all 14 goals with descriptions.

Asset Types

The asset_type parameter helps the model understand the context of your creative:

Social Media Post (default), Advertisement, Product Photo, Email Header, Landing Page Hero, Logo, Website Banner, Illustration

Advanced Patterns

Multi-Goal Scoring

Score the same assets across multiple goals to understand performance across funnel stages:

const goals = ['scroll-stopping', 'click-through-rate', 'purchase-intent'];
const assets = [
  { url: 'https://cdn.example.com/ad-a.png', id: 'ad-a' },
  { url: 'https://cdn.example.com/ad-b.png', id: 'ad-b' },
];

const results = await Promise.all(
  goals.map(goal =>
    rankCreatives(apiKey, { assets, audience, goal })
  )
);

// Build a performance matrix
for (const asset of assets) {
  console.log(`
${asset.id}:`);
  for (let i = 0; i < goals.length; i++) {
    const ranked = results[i].ranked.find(r => r.asset_id === asset.id);
    console.log(`  ${goals[i]}: ${ranked?.score.toFixed(2)}`);
  }
}

Score-Then-Improve Loop

Combine scoring with edit recommendations for an iterative improvement workflow:

// 1. Score the creative
const rankResult = await rankCreatives(apiKey, {
  assets: [{ url: adUrl, id: 'current' }],
  audience,
  goal: 'purchase-intent'
});

const scored = rankResult.ranked[0];

// 2. If score is below threshold, get improvement suggestions
if (scored.score < 3.5) {
  const editRes = await fetch('https://kettio.com/api/rank-assets/recommend-edits', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      image_url: adUrl,
      asset_type: 'Social Media Post',
      evaluation_goal: 'purchase-intent',
      audience_description: audience.description,
      score: scored.score,
      rationale: scored.rationale
    })
  });

  const { recommendations } = await editRes.json();
  // Each recommendation has: title, why, instructions
  // You can feed instructions directly to an image generation API
}

Multi-Audience Scoring

Test the same creative against different audience segments to find universal winners or segment-specific champions:

const audiences = [
  { name: 'Gen Z', demographics: { ageRange: '18-24', platformFatigue: 'very-high' } },
  { name: 'Millennials', demographics: { ageRange: '25-34', platformFatigue: 'high' } },
  { name: 'Gen X', demographics: { ageRange: '45-54', platformFatigue: 'medium' } },
];

const results = await Promise.all(
  audiences.map(audience =>
    rankCreatives(apiKey, {
      assets: [{ url: adUrl, id: 'hero-ad' }],
      audience,
      goal: 'purchase-intent'
    })
  )
);

for (let i = 0; i < audiences.length; i++) {
  const score = results[i].ranked[0].score;
  console.log(`${audiences[i].name}: ${score.toFixed(2)}`);
}

Error Handling

Status Meaning What to Do
401 Invalid or missing API key Check your Authorization header
400 Invalid request body Check required fields: assets + (audience or audience_id)
402 Insufficient credits Top up credits in your dashboard
429 Rate limited (60 assets/min) Back off and retry after 60 seconds

Individual asset failures appear in the errors array without failing the whole request. Always check both ranked and errors:

if (result.errors.length > 0) {
  console.warn('Some assets failed:', result.errors);
}
// Successfully ranked assets are still in result.ranked

Try the Rank API

Sign up, grab your API key, and score your first creative in under 5 minutes.

Get Started Free →

Frequently Asked Questions

How many credits does each ranking call cost?

Each successfully ranked asset costs 1 credit. If you submit 5 images, you'll use 5 credits. Failed assets (broken URLs, etc.) don't cost credits.

What image formats are supported?

PNG, JPEG, WebP, and GIF (first frame). Images must be publicly accessible via URL. Maximum resolution is 4096x4096.

Can I rank video creatives?

The Rank API currently scores static images. Video scoring is available in the Kettio dashboard. An API endpoint for video is coming soon.

How long does a ranking call take?

Typically 5-15 seconds depending on the number of assets. Each asset is evaluated with multiple model samples, which takes time but produces more reliable scores.

Can I use a saved audience from the Kettio dashboard?

Yes. Create audiences in the dashboard or via the Audiences API, then pass the audience ID with audience_id instead of an inline audience object.

AI ad scoringcreative scoring APIrank APIad testingSSRcreative testing