{
  "openapi": "3.1.0",
  "info": {
    "title": "Kettio Rank API",
    "version": "1.1.0",
    "description": "Score and rank batches of ad creative assets with Kettio's SSR pipeline, then validate selected matchups with a five-voter Haiku Bradley-Terry panel."
  },
  "servers": [
    {
      "url": "https://kettio.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/api/v1/rank": {
      "post": {
        "operationId": "rankAssets",
        "summary": "Rank ad creative assets",
        "description": "Ranks 1 to 20 image assets against an inline or saved audience. Assets with copy_context are scored as the full ad package and also receive an image-only ablation for copy lift.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RankRequest"
              },
              "examples": {
                "imageOnlyBatch": {
                  "summary": "Image-only batch rank",
                  "value": {
                    "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",
                      "description": "Parents aged 30-44 who compare prices before buying",
                      "demographics": {
                        "ageRange": "35-44",
                        "priceSensitivity": "high",
                        "shoppingIntent": "comparing"
                      }
                    },
                    "goal": "purchase-intent",
                    "platform": "meta"
                  }
                },
                "fullAdPackage": {
                  "summary": "Full ad package with copy context",
                  "value": {
                    "assets": [
                      {
                        "url": "https://cdn.example.com/ad-a.png",
                        "id": "ad-a",
                        "asset_type": "Advertisement",
                        "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"
                        }
                      }
                    ],
                    "audience": {
                      "name": "Millennial pet owners",
                      "description": "Dog owners aged 25-34, mid-income, active on Instagram"
                    },
                    "goal": "purchase-intent",
                    "campaign_type": "meta-feed",
                    "refine_close_pairs": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ranked assets and any per-asset errors.",
            "headers": {
              "X-Request-Id": {
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "string"
                },
                "description": "Remaining scoring evaluations in the current rate-limit window."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RankResponse"
                },
                "example": {
                  "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": "Advertisement",
                      "copy_context_included": true,
                      "image_only_score": 3.81,
                      "copy_lift": 0.42,
                      "rationale": "The creative quickly communicates the offer and the copy 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
                      },
                      "details": {
                        "probabilities": [
                          0.02,
                          0.06,
                          0.18,
                          0.46,
                          0.28
                        ],
                        "similarities": [
                          0.12,
                          0.21,
                          0.35,
                          0.47,
                          0.41
                        ],
                        "repetitionCount": 3,
                        "generatorSampleCount": 6,
                        "repetitionScores": [4.18, 4.24, 4.3],
                        "repetitionScoreRange": 0.12
                      }
                    }
                  ],
                  "errors": [],
                  "summary": {
                    "goal": "purchase-intent",
                    "asset_type": "Social Media Post",
                    "platform": "meta",
                    "audience": "Millennial pet owners",
                    "assets_ranked": 1,
                    "assets_failed": 0,
                    "scoring_evaluations": 2,
                    "repetitions_per_evaluation": 3,
                    "generator_samples_per_evaluation": 6,
                    "close_pair_refinement": {
                      "enabled": true,
                      "pairs_evaluated": 0,
                      "results": []
                    },
                    "credits_used": 2,
                    "credits_remaining": 48
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "402": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "422": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitError"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/v1/pairwise": {
      "post": {
        "operationId": "compareCreativePairs",
        "summary": "Run five-voter Haiku pairwise comparisons",
        "description": "Evaluates 1 to 10 explicit creative pairs with five blinded, presentation-balanced Claude Haiku votes per pair. Every vote and model response is returned for auditability.",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PairwiseRequest"
              },
              "example": {
                "pairs": [
                  {
                    "pair_id": "creative-a-vs-b",
                    "a": {
                      "id": "creative-a",
                      "image_url": "https://cdn.example.com/ad-a.png"
                    },
                    "b": {
                      "id": "creative-b",
                      "image_url": "https://cdn.example.com/ad-b.png"
                    }
                  }
                ],
                "audience": {
                  "name": "GLP-1 shoppers",
                  "description": "Women considering GLP-1 support who need credible purchase proof"
                },
                "goal": "purchase-intent",
                "platform": "facebook"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Completed pairwise panels and individual votes.",
            "headers": {
              "X-Request-Id": {
                "schema": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PairwiseResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "402": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "422": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitError"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Kettio API key, for example Authorization: Bearer agk_live_xxx."
      }
    },
    "schemas": {
      "RankRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "assets"
        ],
        "properties": {
          "assets": {
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/AssetInput"
            }
          },
          "audience": {
            "$ref": "#/components/schemas/InlineAudience"
          },
          "audience_id": {
            "type": "string",
            "description": "Saved audience ID from the authenticated Kettio account."
          },
          "goal": {
            "type": "string",
            "default": "purchase-intent"
          },
          "asset_type": {
            "type": "string",
            "default": "Social Media Post",
            "description": "Default asset type for assets that do not provide their own asset_type."
          },
          "platform": {
            "type": "string",
            "description": "Optional platform context, for example meta, instagram, facebook, tiktok, linkedin, or google."
          },
          "campaign_type": {
            "type": "string",
            "description": "Optional campaign placement/context alias used when platform is omitted."
          },
          "refine_close_pairs": {
            "type": "boolean",
            "default": true,
            "description": "When true, Kettio can run a pairwise panel on near-tied successful assets and adjust rank ordering when the panel disagrees."
          }
        },
        "oneOf": [
          {
            "required": [
              "audience"
            ],
            "not": {
              "required": [
                "audience_id"
              ]
            }
          },
          {
            "required": [
              "audience_id"
            ],
            "not": {
              "required": [
                "audience"
              ]
            }
          }
        ]
      },
      "AssetInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "HTTPS image URL or data:image URI."
          },
          "id": {
            "type": "string",
            "description": "Optional caller-supplied ID returned as asset_id."
          },
          "asset_type": {
            "type": "string",
            "description": "Per-asset override for the request asset_type."
          },
          "copy_context": {
            "$ref": "#/components/schemas/CopyContext"
          }
        }
      },
      "CopyContext": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "pageName": {
            "type": "string"
          },
          "adBody": {
            "type": "string"
          },
          "adHeadline": {
            "type": "string"
          },
          "adCaption": {
            "type": "string"
          },
          "adDescription": {
            "type": "string"
          },
          "primaryText": {
            "type": "string",
            "description": "Alias for adBody."
          },
          "headline": {
            "type": "string",
            "description": "Alias for adHeadline."
          },
          "caption": {
            "type": "string",
            "description": "Alias for adCaption."
          },
          "description": {
            "type": "string",
            "description": "Alias for adDescription when used inside copy_context."
          }
        },
        "description": "Structured ad copy. When present, the asset is scored as a full ad package and Kettio also runs an image-only ablation."
      },
      "InlineAudience": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "demographics": {
            "type": "object",
            "additionalProperties": true,
            "properties": {
              "ageRange": {
                "type": "string"
              },
              "incomeLevel": {
                "type": "string"
              },
              "adSkepticism": {
                "type": "string"
              },
              "trustBaseline": {
                "type": "string"
              },
              "priceSensitivity": {
                "type": "string"
              },
              "categoryFamiliarity": {
                "type": "string"
              },
              "shoppingIntent": {
                "type": "string"
              },
              "urgency": {
                "type": "string"
              },
              "brandFamiliarity": {
                "type": "string"
              },
              "platformFatigue": {
                "type": "string"
              }
            }
          }
        }
      },
      "RankResponse": {
        "type": "object",
        "required": [
          "request_id",
          "ranked",
          "errors",
          "summary"
        ],
        "properties": {
          "request_id": {
            "type": "string",
            "format": "uuid"
          },
          "ranked": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RankedAsset"
            }
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RankAssetError"
            }
          },
          "summary": {
            "$ref": "#/components/schemas/RankSummary"
          }
        }
      },
      "PairwiseRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "pairs"
        ],
        "properties": {
          "pairs": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/PairwisePair"
            }
          },
          "audience": {
            "$ref": "#/components/schemas/InlineAudience"
          },
          "audience_id": {
            "type": "string"
          },
          "goal": {
            "type": "string",
            "default": "purchase-intent"
          },
          "platform": {
            "type": "string"
          },
          "campaign_type": {
            "type": "string"
          }
        }
      },
      "PairwisePair": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "pair_id",
          "a",
          "b"
        ],
        "properties": {
          "pair_id": {
            "type": "string"
          },
          "a": {
            "$ref": "#/components/schemas/PairwiseAsset"
          },
          "b": {
            "$ref": "#/components/schemas/PairwiseAsset"
          }
        }
      },
      "PairwiseAsset": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "image_url"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "image_url": {
            "type": "string"
          }
        }
      },
      "PairwiseResponse": {
        "type": "object",
        "required": [
          "request_id",
          "pairs",
          "summary"
        ],
        "properties": {
          "request_id": {
            "type": "string",
            "format": "uuid"
          },
          "pairs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PairwiseResult"
            }
          },
          "summary": {
            "$ref": "#/components/schemas/PairwiseSummary"
          }
        }
      },
      "PairwiseResult": {
        "type": "object",
        "required": [
          "pairId",
          "aId",
          "bId",
          "status",
          "votes"
        ],
        "properties": {
          "pairId": {
            "type": "string"
          },
          "aId": {
            "type": "string"
          },
          "bId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "done",
              "partial",
              "error"
            ]
          },
          "winnerId": {
            "type": "string"
          },
          "votesFor": {
            "type": "integer"
          },
          "votesTotal": {
            "type": "integer"
          },
          "winRate": {
            "type": "number"
          },
          "consistency": {
            "type": "number"
          },
          "votes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PairwiseVote"
            }
          },
          "error": {
            "type": "string"
          }
        }
      },
      "PairwiseVote": {
        "type": "object",
        "required": [
          "voterId",
          "order",
          "winnerId",
          "loserId",
          "response"
        ],
        "properties": {
          "voterId": {
            "type": "string"
          },
          "order": {
            "type": "string",
            "enum": [
              "ab",
              "ba"
            ]
          },
          "winnerId": {
            "type": "string"
          },
          "loserId": {
            "type": "string"
          },
          "response": {
            "type": "string"
          }
        }
      },
      "PairwiseSummary": {
        "type": "object",
        "properties": {
          "goal": {
            "type": "string"
          },
          "platform": {
            "type": [
              "string",
              "null"
            ]
          },
          "audience": {
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "panel_size": {
            "type": "integer",
            "const": 5
          },
          "pairs_requested": {
            "type": "integer"
          },
          "pairs_completed": {
            "type": "integer"
          },
          "pairs_failed": {
            "type": "integer"
          },
          "valid_votes": {
            "type": "integer"
          },
          "pairwise_evaluations": {
            "type": "integer"
          },
          "credits_used": {
            "type": "integer"
          },
          "credits_remaining": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "RankedAsset": {
        "type": "object",
        "required": [
          "rank",
          "asset_url",
          "asset_id",
          "score",
          "score_before_refine",
          "score_layer",
          "asset_type",
          "copy_context_included",
          "image_only_score",
          "copy_lift",
          "rationale",
          "product_read",
          "confidence",
          "panel_outcome",
          "confidence_details",
          "details"
        ],
        "properties": {
          "rank": {
            "type": "integer",
            "minimum": 1
          },
          "asset_url": {
            "type": "string"
          },
          "asset_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "score": {
            "type": "number",
            "minimum": 1,
            "maximum": 5
          },
          "score_before_refine": {
            "type": "number",
            "description": "SSR score before close-pair refinement adjusted rank ordering."
          },
          "score_layer": {
            "type": "string",
            "enum": [
              "image-only",
              "copy-only",
              "full-ad-package"
            ]
          },
          "asset_type": {
            "type": "string"
          },
          "copy_context_included": {
            "type": "boolean"
          },
          "image_only_score": {
            "type": [
              "number",
              "null"
            ],
            "description": "Image-only ablation score. For image-only requests this equals score."
          },
          "copy_lift": {
            "type": [
              "number",
              "null"
            ],
            "description": "Full-ad-package score minus image_only_score when copy_context is present."
          },
          "rationale": {
            "type": "string"
          },
          "product_read": {
            "type": "string"
          },
          "confidence": {
            "type": "string",
            "enum": [
              "high",
              "medium",
              "low"
            ]
          },
          "panel_outcome": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PanelOutcome"
              },
              {
                "type": "null"
              }
            ]
          },
          "confidence_details": {
            "$ref": "#/components/schemas/ConfidenceDetails"
          },
          "details": {
            "$ref": "#/components/schemas/ScoreDetails"
          }
        }
      },
      "PanelOutcome": {
        "type": "object",
        "properties": {
          "winnerAssetId": {
            "type": "string"
          },
          "loserAssetId": {
            "type": "string"
          },
          "flipped": {
            "type": "boolean"
          },
          "votesFor": {
            "type": "integer"
          },
          "votesTotal": {
            "type": "integer"
          },
          "consistency": {
            "type": "number"
          }
        }
      },
      "ConfidenceDetails": {
        "type": "object",
        "properties": {
          "entropy": {
            "type": "number"
          },
          "top_margin": {
            "type": "number"
          },
          "sample_std_dev": {
            "type": "number"
          },
          "repetition_count": {
            "type": "integer",
            "const": 3
          },
          "generator_sample_count": {
            "type": "integer",
            "const": 6
          }
        }
      },
      "ScoreDetails": {
        "type": "object",
        "properties": {
          "probabilities": {
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "similarities": {
            "type": "array",
            "items": {
              "type": "number"
            }
          },
          "repetitionCount": {
            "type": "integer",
            "const": 3
          },
          "generatorSampleCount": {
            "type": "integer",
            "const": 6
          },
          "repetitionScores": {
            "type": "array",
            "minItems": 3,
            "maxItems": 3,
            "items": {
              "type": "number"
            }
          },
          "repetitionScoreRange": {
            "type": "number"
          }
        }
      },
      "RankAssetError": {
        "type": "object",
        "properties": {
          "asset_url": {
            "type": "string"
          },
          "asset_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "error": {
            "type": "string"
          }
        }
      },
      "RankSummary": {
        "type": "object",
        "properties": {
          "goal": {
            "type": "string"
          },
          "asset_type": {
            "type": "string"
          },
          "platform": {
            "type": [
              "string",
              "null"
            ]
          },
          "audience": {
            "type": "string"
          },
          "assets_ranked": {
            "type": "integer"
          },
          "assets_failed": {
            "type": "integer"
          },
          "scoring_evaluations": {
            "type": "integer",
            "description": "Successful score evaluations billed and counted against rate limits. Image-only assets use 1. Assets with copy_context use up to 2."
          },
          "repetitions_per_evaluation": {
            "type": "integer",
            "const": 3,
            "description": "Independent paired-model repetitions used for every scoring evaluation."
          },
          "generator_samples_per_evaluation": {
            "type": "integer",
            "const": 6,
            "description": "Three Flash samples plus three Haiku samples used for every scoring evaluation."
          },
          "close_pair_refinement": {
            "$ref": "#/components/schemas/ClosePairRefinement"
          },
          "credits_used": {
            "type": "integer"
          },
          "credits_remaining": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "ClosePairRefinement": {
        "type": "object",
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "pairs_evaluated": {
            "type": "integer"
          },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "field": {
            "type": "string"
          }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Error response.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "RateLimitError": {
        "description": "Rate limit response.",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                {
                  "type": "object",
                  "properties": {
                    "retryAfter": {
                      "type": "integer"
                    },
                    "resetAt": {
                      "type": "integer"
                    }
                  }
                }
              ]
            }
          }
        }
      }
    }
  }
}
