> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kettio.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Your first ranking

> Send a batch of creatives to the Rank API and read the response.

This walks through one complete `POST /api/v1/rank` call: what to send, what comes back, and
which fields actually drive a decision.

## Send the request

<CodeGroup>
  ```bash cURL theme={null}
  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://cdn.example.com/ad-a.png",
          "id": "ad-a",
          "copy_context": {
            "pageName": "Pawbox",
            "adBody": "First box free for new subscribers. Fresh treats picked for picky dogs.",
            "adHeadline": "Healthy treats your dog will actually want",
            "adCaption": "Cancel anytime"
          }
        },
        { "url": "https://cdn.example.com/ad-b.png", "id": "ad-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",
      "platform": "meta",
      "refine_close_pairs": true
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://kettio.com/api/v1/rank", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.KETTIO_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      assets: [
        {
          url: "https://cdn.example.com/ad-a.png",
          id: "ad-a",
          copy_context: {
            pageName: "Pawbox",
            adBody: "First box free for new subscribers. Fresh treats picked for picky dogs.",
            adHeadline: "Healthy treats your dog will actually want",
            adCaption: "Cancel anytime",
          },
        },
        { url: "https://cdn.example.com/ad-b.png", id: "ad-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",
      platform: "meta",
      refine_close_pairs: true,
    }),
  });

  const { ranked, summary } = await response.json();
  ```

  ```python Python theme={null}
  import os, requests

  response = requests.post(
      "https://kettio.com/api/v1/rank",
      headers={"Authorization": f"Bearer {os.environ['KETTIO_API_KEY']}"},
      json={
          "assets": [
              {
                  "url": "https://cdn.example.com/ad-a.png",
                  "id": "ad-a",
                  "copy_context": {
                      "pageName": "Pawbox",
                      "adBody": "First box free for new subscribers. Fresh treats picked for picky dogs.",
                      "adHeadline": "Healthy treats your dog will actually want",
                      "adCaption": "Cancel anytime",
                  },
              },
              {"url": "https://cdn.example.com/ad-b.png", "id": "ad-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",
          "platform": "meta",
          "refine_close_pairs": True,
      },
  )

  payload = response.json()
  ```
</CodeGroup>

Only two things are strictly required: at least one asset with a `url`, and an audience — either
inline (`audience`) or a saved one (`audience_id`). Everything else has a default.

## Read the response

```json theme={null}
{
  "request_id": "1f4d884c-f9ef-4f36-a8e9-f7b6a5b63291",
  "ranked": [
    {
      "rank": 1,
      "asset_url": "https://cdn.example.com/ad-a.png",
      "asset_id": "ad-a",
      "score": 4.24,
      "score_before_refine": 4.23,
      "score_layer": "full-ad-package",
      "asset_type": "Social Media Post",
      "copy_context_included": true,
      "image_only_score": 3.81,
      "copy_lift": 0.42,
      "rationale": "The creative quickly communicates the offer and removes purchase friction.",
      "product_read": "A product-focused social ad with subscription-box imagery and a clear first-box-free offer.",
      "confidence": "high",
      "panel_outcome": null,
      "confidence_details": {
        "entropy": 0.38,
        "top_margin": 0.35,
        "sample_std_dev": 0.06,
        "repetition_count": 3,
        "generator_sample_count": 6
      }
    }
  ],
  "errors": [],
  "summary": {
    "goal": "purchase-intent",
    "assets_ranked": 1,
    "assets_failed": 0,
    "scoring_evaluations": 2,
    "credits_used": 2,
    "credits_remaining": 48
  }
}
```

### The fields that matter

<AccordionGroup>
  <Accordion title="rank and score" icon="list-ol">
    `ranked` comes back sorted. `score` is the final value after any close-pair refinement;
    `score_before_refine` is the raw SSR score. Both are relative within this batch.
  </Accordion>

  <Accordion title="copy_lift and image_only_score" icon="quote-left">
    Present only when you sent `copy_context` and the image-only ablation succeeded.
    `copy_lift` is the difference between the full-ad-package score and the image-only score —
    what the words added on top of the picture.
  </Accordion>

  <Accordion title="confidence and confidence_details" icon="scale-balanced">
    `confidence` is a coarse label. `confidence_details` carries the numbers behind it,
    including `sample_std_dev` across repetitions. A large spread means the pipeline disagreed
    with itself and the ordering is soft. See [Reading confidence](/concepts/confidence).
  </Accordion>

  <Accordion title="panel_outcome" icon="gavel">
    Non-null only when close-pair refinement ran a pairwise panel on this asset. It reports the
    winner, whether the panel flipped the original SSR order, the votes, and consistency.
  </Accordion>

  <Accordion title="errors" icon="triangle-exclamation">
    Per-asset failures land here rather than failing the whole request. A batch can succeed with
    `assets_failed > 0` — always check it.
  </Accordion>
</AccordionGroup>

<Warning>
  `summary.credits_used` is **2** in this example for **2** assets — but that is a coincidence.
  The asset with `copy_context` cost 2 evaluations (full package plus image-only ablation) and the
  bare image asset cost 1, minus failures. Budget against `scoring_evaluations`, never against
  asset count. See [Limits and billing](/concepts/limits-and-billing).
</Warning>

## Next

<CardGroup cols={2}>
  <Card title="Rank API guide" icon="sliders" href="/guides/rank-api">
    Every request field, copy-context aliases, and batch refinement.
  </Card>

  <Card title="Pairwise API" icon="code-compare" href="/guides/pairwise-api">
    When you need a direct five-voter comparison instead of a score.
  </Card>
</CardGroup>
