{
  "openapi": "3.1.0",
  "info": {
    "title": "Klinky Public API",
    "description": "The Klinky API enables programmatic management of A/B testing links. Create smart links with multiple variants, track clicks and conversions, and optimize your marketing campaigns through data-driven decisions.",
    "version": "1.0.0",
    "contact": {
      "name": "Klinky Support",
      "url": "https://klinky.io/support",
      "email": "support@klinky.io"
    },
    "license": {
      "name": "MIT",
      "url": "https://opensource.org/licenses/MIT"
    }
  },
  "servers": [
    {
      "url": "https://klinky-api.fly.dev/api/v1",
      "description": "Production API"
    },
    {
      "url": "https://klinky-api-staging.fly.dev/api/v1",
      "description": "Staging API"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "paths": {
    "/public/keys": {
      "post": {
        "summary": "Create API key",
        "description": "Create a new API key for programmatic access. The full key is returned only once and cannot be retrieved later. Store it securely. Requires JWT authentication via Authorization header.",
        "operationId": "createApiKey",
        "tags": ["API Keys"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "description": "Descriptive name for this API key"
          }
        ],
        "responses": {
          "201": {
            "description": "API key created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIKeyCreateResponse"
                },
                "example": {
                  "id": "550e8400-e29b-41d4-a716-446655440000",
                  "name": "Production API Key",
                  "key": "klinky_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
                  "key_prefix": "klinky_live_a1b2",
                  "created_at": "2024-01-15T10:30:00Z",
                  "rate_limit": 1000
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl -X POST 'https://klinky-api.fly.dev/api/v1/public/keys?name=Production%20API%20Key' \\n  -H 'Authorization: Bearer <your-jwt-token>'"
          },
          {
            "lang": "Python",
            "source": "import requests\n\nresponse = requests.post(\n    'https://klinky-api.fly.dev/api/v1/public/keys',\n    params={'name': 'Production API Key'},\n    headers={'Authorization': 'Bearer <your-jwt-token>'}\n)\n\ndata = response.json()\nprint(f\"API Key: {data['key']}\")  # Save this - shown only once!"
          }
        ]
      },
      "get": {
        "summary": "List API keys",
        "description": "List all active API keys for the authenticated user. Returns key metadata without the actual key values.",
        "operationId": "listApiKeys",
        "tags": ["API Keys"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "List of API keys",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/APIKeyListResponse"
                },
                "example": {
                  "keys": [
                    {
                      "id": "550e8400-e29b-41d4-a716-446655440000",
                      "name": "Production API Key",
                      "key_prefix": "klinky_live_a1b2",
                      "created_at": "2024-01-15T10:30:00Z",
                      "last_used_at": "2024-01-15T14:22:00Z",
                      "is_active": true
                    }
                  ],
                  "rate_limit_per_hour": 1000
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl 'https://klinky-api.fly.dev/api/v1/public/keys' \\n  -H 'Authorization: Bearer <your-jwt-token>'"
          },
          {
            "lang": "Python",
            "source": "import requests\n\nresponse = requests.get(\n    'https://klinky-api.fly.dev/api/v1/public/keys',\n    headers={'Authorization': 'Bearer <your-jwt-token>'}\n)\n\nkeys = response.json()['keys']\nfor key in keys:\n    print(f\"{key['name']}: {key['key_prefix']}...\")"
          }
        ]
      }
    },
    "/public/keys/{key_id}": {
      "delete": {
        "summary": "Revoke API key",
        "description": "Revoke an API key by ID. This immediately disables the key and prevents further API access.",
        "operationId": "revokeApiKey",
        "tags": ["API Keys"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "key_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID of the API key to revoke"
          }
        ],
        "responses": {
          "204": {
            "description": "API key revoked successfully"
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl -X DELETE 'https://klinky-api.fly.dev/api/v1/public/keys/550e8400-e29b-41d4-a716-446655440000' \\n  -H 'Authorization: Bearer <your-jwt-token>'"
          },
          {
            "lang": "Python",
            "source": "import requests\n\nkey_id = '550e8400-e29b-41d4-a716-446655440000'\nresponse = requests.delete(\n    f'https://klinky-api.fly.dev/api/v1/public/keys/{key_id}',\n    headers={'Authorization': 'Bearer <your-jwt-token>'}\n)\n\nif response.status_code == 204:\n    print('API key revoked successfully')"
          }
        ]
      }
    },
    "/public/keys/rate-limit": {
      "get": {
        "summary": "Get rate limit status",
        "description": "Get current rate limit status for the authenticated user, including remaining requests and reset time.",
        "operationId": "getRateLimitStatus",
        "tags": ["API Keys"],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Rate limit status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RateLimitStatusResponse"
                },
                "example": {
                  "limit": 1000,
                  "remaining": 847,
                  "reset_at": "2024-01-15T15:00:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl 'https://klinky-api.fly.dev/api/v1/public/keys/rate-limit' \\n  -H 'Authorization: Bearer <your-jwt-token>'"
          },
          {
            "lang": "Python",
            "source": "import requests\nfrom datetime import datetime\n\nresponse = requests.get(\n    'https://klinky-api.fly.dev/api/v1/public/keys/rate-limit',\n    headers={'Authorization': 'Bearer <your-jwt-token>'}\n)\n\ndata = response.json()\nprint(f\"Remaining: {data['remaining']}/{data['limit']}\")\nreset_time = datetime.fromisoformat(data['reset_at'].replace('Z', '+00:00'))\nprint(f\"Resets at: {reset_time}\")"
          }
        ]
      }
    },
    "/public/links": {
      "post": {
        "summary": "Create link",
        "description": "Create a new smart link with multiple variants for A/B testing. Each variant can have a different destination URL and traffic weight. Weights must sum to exactly 100.",
        "operationId": "createLink",
        "tags": ["Links"],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Unique key to prevent duplicate link creation. If a link with this key already exists, it will be returned instead of creating a new one."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LinkCreate"
              },
              "examples": {
                "simple-ab-test": {
                  "summary": "Simple A/B test",
                  "value": {
                    "name": "Homepage Test",
                    "slug": "homepage-test",
                    "variants": [
                      {
                        "label": "control",
                        "destination_url": "https://example.com/control",
                        "weight": 50
                      },
                      {
                        "label": "variant_b",
                        "destination_url": "https://example.com/variant-b",
                        "weight": 50
                      }
                    ],
                    "routing_type": "ab_test"
                  }
                },
                "geo-routing": {
                  "summary": "Geo-based routing",
                  "value": {
                    "name": "Regional Landing Pages",
                    "variants": [
                      {
                        "label": "us_page",
                        "destination_url": "https://example.com/us",
                        "weight": 40
                      },
                      {
                        "label": "eu_page",
                        "destination_url": "https://example.com/eu",
                        "weight": 30
                      },
                      {
                        "label": "default_page",
                        "destination_url": "https://example.com/global",
                        "weight": 30
                      }
                    ],
                    "routing_type": "geo",
                    "geo_rules": {
                      "US": "us_page",
                      "GB": "us_page",
                      "DE": "eu_page",
                      "FR": "eu_page",
                      "default": "default_page"
                    }
                  }
                },
                "auto-winner": {
                  "summary": "Auto-winner detection",
                  "value": {
                    "name": "Product Launch Test",
                    "auto_winner": true,
                    "auto_winner_threshold": 100,
                    "variants": [
                      {
                        "label": "version_a",
                        "destination_url": "https://example.com/a",
                        "weight": 50
                      },
                      {
                        "label": "version_b",
                        "destination_url": "https://example.com/b",
                        "weight": 50
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Link created successfully",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkResponse"
                }
              }
            }
          },
          "200": {
            "description": "Link already exists (idempotency key match)",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "409": {
            "$ref": "#/components/responses/ConflictError"
          },
          "422": {
            "$ref": "#/components/responses/ValidationError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl -X POST 'https://klinky-api.fly.dev/api/v1/public/links' \\n  -H 'X-API-Key: klinky_live_your_api_key_here' \\n  -H 'Content-Type: application/json' \\n  -d '{\n    \"name\": \"Homepage Test\",\n    \"slug\": \"homepage-test\",\n    \"variants\": [\n      {\n        \"label\": \"control\",\n        \"destination_url\": \"https://example.com/control\",\n        \"weight\": 50\n      },\n      {\n        \"label\": \"variant_b\",\n        \"destination_url\": \"https://example.com/variant-b\",\n        \"weight\": 50\n      }\n    ]\n  }'"
          },
          {
            "lang": "Python",
            "source": "import requests\n\nlink_data = {\n    'name': 'Homepage Test',\n    'slug': 'homepage-test',\n    'variants': [\n        {\n            'label': 'control',\n            'destination_url': 'https://example.com/control',\n            'weight': 50\n        },\n        {\n            'label': 'variant_b',\n            'destination_url': 'https://example.com/variant-b',\n            'weight': 50\n        }\n    ],\n    'routing_type': 'ab_test'\n}\n\nresponse = requests.post(\n    'https://klinky-api.fly.dev/api/v1/public/links',\n    headers={'X-API-Key': 'klinky_live_your_api_key_here'},\n    json=link_data\n)\n\nlink = response.json()\nprint(f\"Created link: klinky.io/{link['slug']}\")"
          },
          {
            "lang": "JavaScript",
            "source": "const response = await fetch('https://klinky-api.fly.dev/api/v1/public/links', {\n  method: 'POST',\n  headers: {\n    'X-API-Key': 'klinky_live_your_api_key_here',\n    'Content-Type': 'application/json'\n  },\n  body: JSON.stringify({\n    name: 'Homepage Test',\n    slug: 'homepage-test',\n    variants: [\n      { label: 'control', destination_url: 'https://example.com/control', weight: 50 },\n      { label: 'variant_b', destination_url: 'https://example.com/variant-b', weight: 50 }\n    ]\n  })\n});\n\nconst link = await response.json();\nconsole.log(`Created link: klinky.io/${link.slug}`);"
          }
        ]
      },
      "get": {
        "summary": "List links",
        "description": "List all links for the authenticated user with pagination. Returns basic stats including total clicks and conversions for each link.",
        "operationId": "listLinks",
        "tags": ["Links"],
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "description": "Page number for pagination"
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Number of items per page"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of links",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkListResponse"
                },
                "example": {
                  "items": [
                    {
                      "id": "550e8400-e29b-41d4-a716-446655440000",
                      "slug": "homepage-test",
                      "name": "Homepage Test",
                      "is_active": true,
                      "routing_type": "ab_test",
                      "created_at": "2024-01-15T10:30:00Z",
                      "total_clicks": 1523,
                      "total_conversions": 127,
                      "top_variant": "variant_b"
                    }
                  ],
                  "total": 42
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl 'https://klinky-api.fly.dev/api/v1/public/links?page=1&per_page=20' \\n  -H 'X-API-Key: klinky_live_your_api_key_here'"
          },
          {
            "lang": "Python",
            "source": "import requests\n\nresponse = requests.get(\n    'https://klinky-api.fly.dev/api/v1/public/links',\n    headers={'X-API-Key': 'klinky_live_your_api_key_here'},\n    params={'page': 1, 'per_page': 20}\n)\n\ndata = response.json()\nfor link in data['items']:\n    print(f\"{link['name']}: {link['total_clicks']} clicks\")"
          }
        ]
      }
    },
    "/public/links/{id}": {
      "get": {
        "summary": "Get link details",
        "description": "Get detailed information about a specific link including all variants and their current status.",
        "operationId": "getLink",
        "tags": ["Links"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID of the link"
          }
        ],
        "responses": {
          "200": {
            "description": "Link details",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl 'https://klinky-api.fly.dev/api/v1/public/links/550e8400-e29b-41d4-a716-446655440000' \\n  -H 'X-API-Key: klinky_live_your_api_key_here'"
          },
          {
            "lang": "Python",
            "source": "import requests\n\nlink_id = '550e8400-e29b-41d4-a716-446655440000'\nresponse = requests.get(\n    f'https://klinky-api.fly.dev/api/v1/public/links/{link_id}',\n    headers={'X-API-Key': 'klinky_live_your_api_key_here'}\n)\n\nlink = response.json()\nprint(f\"Link: klinky.io/{link['slug']}\")\nfor variant in link['variants']:\n    print(f\"  - {variant['label']}: {variant['destination_url']}\")"
          }
        ]
      },
      "delete": {
        "summary": "Delete link",
        "description": "Soft delete a link. The link will no longer be accessible but data is preserved for analytics.",
        "operationId": "deleteLink",
        "tags": ["Links"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID of the link to delete"
          }
        ],
        "responses": {
          "204": {
            "description": "Link deleted successfully",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl -X DELETE 'https://klinky-api.fly.dev/api/v1/public/links/550e8400-e29b-41d4-a716-446655440000' \\n  -H 'X-API-Key: klinky_live_your_api_key_here'"
          },
          {
            "lang": "Python",
            "source": "import requests\n\nlink_id = '550e8400-e29b-41d4-a716-446655440000'\nresponse = requests.delete(\n    f'https://klinky-api.fly.dev/api/v1/public/links/{link_id}',\n    headers={'X-API-Key': 'klinky_live_your_api_key_here'}\n)\n\nif response.status_code == 204:\n    print('Link deleted successfully')"
          }
        ]
      }
    },
    "/public/links/{id}/clicks": {
      "get": {
        "summary": "Get click data",
        "description": "Get paginated click data for a specific link with optional date filtering. Returns detailed click events including referrer, user agent, and conversion status.",
        "operationId": "getLinkClicks",
        "tags": ["Analytics"],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "UUID of the link"
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "description": "Page number for pagination"
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            },
            "description": "Number of items per page"
          },
          {
            "name": "start_date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Filter clicks from this date (ISO 8601 format)"
          },
          {
            "name": "end_date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Filter clicks until this date (ISO 8601 format)"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated click data",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClickListResponse"
                },
                "example": {
                  "items": [
                    {
                      "id": "660e8400-e29b-41d4-a716-446655440000",
                      "link_id": "550e8400-e29b-41d4-a716-446655440000",
                      "variant_id": "770e8400-e29b-41d4-a716-446655440000",
                      "ip_address": "192.168.1.1",
                      "user_agent": "Mozilla/5.0...",
                      "referrer": "https://google.com",
                      "country": "US",
                      "is_conversion": true,
                      "created_at": "2024-01-15T10:30:00Z"
                    }
                  ],
                  "total": 1523,
                  "page": 1,
                  "per_page": 50
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/UnauthorizedError"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenError"
          },
          "404": {
            "$ref": "#/components/responses/NotFoundError"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "cURL",
            "source": "curl 'https://klinky-api.fly.dev/api/v1/public/links/550e8400-e29b-41d4-a716-446655440000/clicks?start_date=2024-01-01&end_date=2024-01-31' \\n  -H 'X-API-Key: klinky_live_your_api_key_here'"
          },
          {
            "lang": "Python",
            "source": "import requests\nfrom datetime import datetime, timedelta\n\nlink_id = '550e8400-e29b-41d4-a716-446655440000'\n\n# Get clicks from last 7 days\nend_date = datetime.utcnow()\nstart_date = end_date - timedelta(days=7)\n\nresponse = requests.get(\n    f'https://klinky-api.fly.dev/api/v1/public/links/{link_id}/clicks',\n    headers={'X-API-Key': 'klinky_live_your_api_key_here'},\n    params={\n        'start_date': start_date.isoformat(),\n        'end_date': end_date.isoformat()\n    }\n)\n\ndata = response.json()\nconversions = sum(1 for click in data['items'] if click['is_conversion'])\nprint(f\"Conversions: {conversions}/{data['total']} clicks\")"
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key for public API endpoints. Format: klinky_live_{32-char-random}"
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "JWT token for API key management endpoints. Obtain from Supabase auth."
      }
    },
    "schemas": {
      "APIKeyCreateResponse": {
        "type": "object",
        "required": ["id", "name", "key", "key_prefix", "created_at", "rate_limit"],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the API key"
          },
          "name": {
            "type": "string",
            "description": "Descriptive name for the API key"
          },
          "key": {
            "type": "string",
            "description": "The full API key. Shown only once - store it securely!"
          },
          "key_prefix": {
            "type": "string",
            "description": "First 16 characters of the key for identification"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the API key was created"
          },
          "rate_limit": {
            "type": "integer",
            "description": "Rate limit per hour for this key based on plan"
          }
        }
      },
      "APIKeyListItem": {
        "type": "object",
        "required": ["id", "name", "key_prefix", "created_at", "is_active"],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "key_prefix": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_used_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "is_active": {
            "type": "boolean"
          }
        }
      },
      "APIKeyListResponse": {
        "type": "object",
        "required": ["keys", "rate_limit_per_hour"],
        "properties": {
          "keys": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/APIKeyListItem"
            }
          },
          "rate_limit_per_hour": {
            "type": "integer",
            "description": "Rate limit per hour based on your plan"
          }
        }
      },
      "RateLimitStatusResponse": {
        "type": "object",
        "required": ["limit", "remaining", "reset_at"],
        "properties": {
          "limit": {
            "type": "integer",
            "description": "Maximum requests allowed per hour"
          },
          "remaining": {
            "type": "integer",
            "description": "Remaining requests in current hour"
          },
          "reset_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the rate limit resets"
          }
        }
      },
      "VariantCreate": {
        "type": "object",
        "required": ["label", "destination_url", "weight"],
        "properties": {
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 50,
            "description": "Unique identifier for this variant (e.g., 'control', 'variant_a')"
          },
          "destination_url": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "format": "uri",
            "description": "URL where traffic for this variant should be sent"
          },
          "weight": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "description": "Percentage of traffic to send to this variant (0-100)"
          }
        }
      },
      "VariantResponse": {
        "type": "object",
        "required": ["id", "link_id", "label", "destination_url", "weight", "is_winner", "created_at"],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "link_id": {
            "type": "string",
            "format": "uuid"
          },
          "label": {
            "type": "string"
          },
          "destination_url": {
            "type": "string",
            "format": "uri"
          },
          "weight": {
            "type": "integer"
          },
          "is_winner": {
            "type": "boolean",
            "description": "Whether this variant has been declared the winner"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "LinkCreate": {
        "type": "object",
        "required": ["name", "variants"],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "Descriptive name for the link"
          },
          "slug": {
            "type": "string",
            "minLength": 3,
            "maxLength": 32,
            "pattern": "^[a-zA-Z0-9_-]+$",
            "description": "Custom short URL path. Auto-generated if not provided."
          },
          "variants": {
            "type": "array",
            "minItems": 2,
            "maxItems": 5,
            "items": {
              "$ref": "#/components/schemas/VariantCreate"
            },
            "description": "Traffic variants for A/B testing. Weights must sum to 100."
          },
          "auto_winner": {
            "type": "boolean",
            "default": false,
            "description": "Enable automatic winner detection based on conversion rate"
          },
          "auto_winner_threshold": {
            "type": "integer",
            "minimum": 10,
            "default": 100,
            "description": "Minimum clicks before auto-winner can be declared"
          },
          "routing_type": {
            "type": "string",
            "enum": ["ab_test", "geo"],
            "default": "ab_test",
            "description": "Traffic routing method"
          },
          "geo_rules": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Country codes mapped to variant labels for geo routing. Must include 'default' key."
          }
        }
      },
      "LinkResponse": {
        "type": "object",
        "required": ["id", "user_id", "slug", "name", "is_active", "auto_winner", "auto_winner_threshold", "routing_type", "created_at", "updated_at", "variants"],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "user_id": {
            "type": "string",
            "format": "uuid"
          },
          "organization_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "is_active": {
            "type": "boolean"
          },
          "auto_winner": {
            "type": "boolean"
          },
          "auto_winner_threshold": {
            "type": "integer"
          },
          "routing_type": {
            "type": "string",
            "enum": ["ab_test", "geo"]
          },
          "geo_rules": {
            "type": "object",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "variants": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VariantResponse"
            }
          }
        }
      },
      "LinkListItem": {
        "type": "object",
        "required": ["id", "slug", "name", "is_active", "routing_type", "created_at"],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "is_active": {
            "type": "boolean"
          },
          "routing_type": {
            "type": "string",
            "enum": ["ab_test", "geo"]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "total_clicks": {
            "type": "integer",
            "default": 0
          },
          "total_conversions": {
            "type": "integer",
            "default": 0
          },
          "top_variant": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "LinkListResponse": {
        "type": "object",
        "required": ["items", "total"],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LinkListItem"
            }
          },
          "total": {
            "type": "integer",
            "description": "Total number of links"
          }
        }
      },
      "ClickEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "link_id": {
            "type": "string",
            "format": "uuid"
          },
          "variant_id": {
            "type": "string",
            "format": "uuid"
          },
          "ip_address": {
            "type": "string"
          },
          "user_agent": {
            "type": "string"
          },
          "referrer": {
            "type": "string",
            "nullable": true
          },
          "country": {
            "type": "string",
            "nullable": true
          },
          "is_conversion": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ClickListResponse": {
        "type": "object",
        "required": ["items", "total", "page", "per_page"],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClickEvent"
            }
          },
          "total": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": ["detail"],
        "properties": {
          "detail": {
            "type": "string",
            "description": "Error message"
          }
        }
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Maximum requests allowed per hour",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Remaining requests in current hour",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Reset": {
        "description": "Unix timestamp when rate limit resets",
        "schema": {
          "type": "integer"
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying (only on 429 responses)",
        "schema": {
          "type": "integer"
        }
      }
    },
    "responses": {
      "UnauthorizedError": {
        "description": "Authentication required - invalid or missing API key",
        "headers": {
          "WWW-Authenticate": {
            "description": "Authentication method required",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "detail": "Missing API key. Include X-API-Key header."
            }
          }
        }
      },
      "ForbiddenError": {
        "description": "Plan does not include API access",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "detail": "Your plan (free) does not include API access. Upgrade to Growth or Scale."
            }
          }
        }
      },
      "NotFoundError": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "detail": "Link not found"
            }
          }
        }
      },
      "ConflictError": {
        "description": "Resource conflict - slug already taken",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "detail": "Slug 'homepage-test' is already taken"
            }
          }
        }
      },
      "ValidationError": {
        "description": "Validation error - invalid request data",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "detail": "Variant weights must sum to 100, got 90"
            }
          }
        }
      },
      "RateLimitError": {
        "description": "Rate limit exceeded",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "detail": "Rate limit exceeded. Please try again later."
            }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "API Keys",
      "description": "Manage API keys for programmatic access. These endpoints require JWT authentication."
    },
    {
      "name": "Links",
      "description": "Create and manage smart links with A/B testing variants."
    },
    {
      "name": "Analytics",
      "description": "Retrieve click data and conversion metrics for your links."
    }
  ]
}
