{
  "openapi": "3.0.3",
  "info": {
    "title": "Lynko.tv API",
    "version": "2026-05-27",
    "description": "Full-platform JSON API for Lynko.tv workspaces. Responses are raw JSON under /api/v1, authenticated with X-API-Key by default or Authorization Bearer when the host forwards Authorization headers to PHP, and protected by plan-based rate limits. Reads use normal limits, writes and analytics summaries use half limits, and exports, webhook tests, and uploads use quarter limits. Public redirect, vote, and capture submit routes stay unchanged."
  },
  "servers": [
    {
      "url": "https://lynko.tv/api/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "apiKeyAuth": []
    }
  ],
  "tags": [
    { "name": "Account" },
    { "name": "Shortlinks" },
    { "name": "QR codes" },
    { "name": "Link pages" },
    { "name": "Polls" },
    { "name": "Campaign Kits" },
    { "name": "Smart Cards" },
    { "name": "Counters" },
    { "name": "Support Hub" },
    { "name": "Capture" },
    { "name": "Creator Drops" },
    { "name": "AMA" },
    { "name": "Analytics" },
    { "name": "Webhooks" }
  ],
  "paths": {
    "/me": {
      "get": {
        "tags": ["Account"],
        "summary": "Read the authenticated account and API key",
        "description": "Returns non-sensitive account and API key metadata for any valid API key. The email and vip_days fields are returned only when the key includes account:read.",
        "x-sensitive-permission": "account:read",
        "x-rate-limit-cost": "read",
        "responses": {
          "200": {
            "$ref": "#/components/responses/ItemOk"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/rate-limits": {
      "get": {
        "tags": ["Account"],
        "summary": "Read current plan limits and endpoint cost rules",
        "x-rate-limit-cost": "read",
        "responses": {
          "200": {
            "$ref": "#/components/responses/ItemOk"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/shortlinks": {
      "get": {
        "tags": ["Shortlinks"],
        "summary": "List short links",
        "x-permission": "links:read",
        "x-rate-limit-cost": "read",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Offset" }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/ListOk"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": ["Shortlinks"],
        "summary": "Create a short link",
        "x-permission": "links:write",
        "x-rate-limit-cost": "write",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ShortlinkCreate"
              },
              "example": {
                "target_url": "https://example.com/post",
                "short_code": "launch",
                "analytics": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/CreatedItem"
          },
          "422": {
            "$ref": "#/components/responses/BadRequest"
          }
        }
      }
    },
    "/shortlinks/{code}": {
      "parameters": [
        { "$ref": "#/components/parameters/ShortCode" }
      ],
      "get": {
        "tags": ["Shortlinks"],
        "summary": "Read a short link",
        "x-permission": "links:read",
        "x-rate-limit-cost": "read",
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "patch": {
        "tags": ["Shortlinks"],
        "summary": "Update a short link",
        "x-permission": "links:write",
        "x-rate-limit-cost": "write",
        "requestBody": {
          "$ref": "#/components/requestBodies/GenericJson"
        },
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": { "$ref": "#/components/responses/BadRequest" }
        }
      },
      "delete": {
        "tags": ["Shortlinks"],
        "summary": "Delete a short link",
        "x-permission": "links:write",
        "x-rate-limit-cost": "write",
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/qr-codes": {
      "get": {
        "tags": ["QR codes"],
        "summary": "List QR codes",
        "x-permission": "qr:read",
        "x-rate-limit-cost": "read",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Offset" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ListOk" }
        }
      },
      "post": {
        "tags": ["QR codes"],
        "summary": "Create a QR code",
        "description": "Optional QR logo upload uses multipart/form-data, requires qr:write plus uploads:write, and is limited to the existing 3 MB source image rule.",
        "x-permission": "qr:write",
        "x-rate-limit-cost": "write or upload",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/QrCreate" }
            },
            "multipart/form-data": {
              "schema": { "$ref": "#/components/schemas/QrCreateMultipart" }
            }
          }
        },
        "responses": {
          "201": { "$ref": "#/components/responses/CreatedItem" },
          "422": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/qr-codes/{code}": {
      "parameters": [
        { "$ref": "#/components/parameters/QrCode" }
      ],
      "get": {
        "tags": ["QR codes"],
        "summary": "Read a QR code",
        "x-permission": "qr:read",
        "x-rate-limit-cost": "read",
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "patch": {
        "tags": ["QR codes"],
        "summary": "Update a QR code",
        "x-permission": "qr:write",
        "x-rate-limit-cost": "write",
        "requestBody": { "$ref": "#/components/requestBodies/GenericJson" },
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": { "$ref": "#/components/responses/BadRequest" }
        }
      },
      "delete": {
        "tags": ["QR codes"],
        "summary": "Delete a QR code",
        "x-permission": "qr:write",
        "x-rate-limit-cost": "write",
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/link-pages": {
      "get": {
        "tags": ["Link pages"],
        "summary": "List link pages",
        "x-permission": "trees:read",
        "x-rate-limit-cost": "read",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Offset" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ListOk" }
        }
      },
      "post": {
        "tags": ["Link pages"],
        "summary": "Create a link page",
        "x-permission": "trees:write",
        "x-rate-limit-cost": "write",
        "requestBody": { "$ref": "#/components/requestBodies/GenericJson" },
        "responses": {
          "201": { "$ref": "#/components/responses/CreatedItem" }
        }
      }
    },
    "/link-pages/{handle}": {
      "parameters": [
        { "$ref": "#/components/parameters/Handle" }
      ],
      "patch": {
        "tags": ["Link pages"],
        "summary": "Update a link page",
        "x-permission": "trees:write",
        "x-rate-limit-cost": "write",
        "requestBody": { "$ref": "#/components/requestBodies/GenericJson" },
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" }
        }
      },
      "delete": {
        "tags": ["Link pages"],
        "summary": "Unpublish a link page",
        "x-permission": "trees:write",
        "x-rate-limit-cost": "write",
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" }
        }
      }
    },
    "/polls": {
      "get": {
        "tags": ["Polls"],
        "summary": "List polls",
        "x-permission": "polls:read",
        "x-rate-limit-cost": "read",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Offset" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ListOk" }
        }
      },
      "post": {
        "tags": ["Polls"],
        "summary": "Create a poll",
        "x-permission": "polls:write",
        "x-rate-limit-cost": "write",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/PollCreate" },
              "example": {
                "question": "Which launch name do you prefer?",
                "options": ["Lemon", "Lime"],
                "duration_minutes": 1440,
                "public_results": true
              }
            }
          }
        },
        "responses": {
          "201": { "$ref": "#/components/responses/CreatedItem" },
          "422": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/polls/{id}": {
      "parameters": [
        { "$ref": "#/components/parameters/NumericId" }
      ],
      "patch": {
        "tags": ["Polls"],
        "summary": "Update poll status or result visibility",
        "x-permission": "polls:write",
        "x-rate-limit-cost": "write",
        "requestBody": { "$ref": "#/components/requestBodies/GenericJson" },
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" }
        }
      },
      "delete": {
        "tags": ["Polls"],
        "summary": "Close a poll",
        "x-permission": "polls:write",
        "x-rate-limit-cost": "write",
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" }
        }
      }
    },
    "/campaign-kits": {
      "get": {
        "tags": ["Campaign Kits"],
        "summary": "List Campaign Kits",
        "x-permission": "campaigns:read",
        "x-rate-limit-cost": "read",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Offset" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ListOk" }
        }
      },
      "post": {
        "tags": ["Campaign Kits"],
        "summary": "Create a Campaign Kit",
        "x-permission": "campaigns:write",
        "x-rate-limit-cost": "write",
        "requestBody": { "$ref": "#/components/requestBodies/GenericJson" },
        "responses": {
          "201": { "$ref": "#/components/responses/CreatedItem" }
        }
      }
    },
    "/campaign-kits/{id}": {
      "parameters": [
        { "$ref": "#/components/parameters/NumericId" }
      ],
      "patch": {
        "tags": ["Campaign Kits"],
        "summary": "Update a Campaign Kit",
        "x-permission": "campaigns:write",
        "x-rate-limit-cost": "write",
        "requestBody": { "$ref": "#/components/requestBodies/GenericJson" },
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" }
        }
      },
      "delete": {
        "tags": ["Campaign Kits"],
        "summary": "Archive a Campaign Kit",
        "x-permission": "campaigns:write",
        "x-rate-limit-cost": "write",
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" }
        }
      }
    },
    "/smart-cards": {
      "get": {
        "tags": ["Smart Cards"],
        "summary": "List Smart Cards",
        "x-permission": "cards:read",
        "x-rate-limit-cost": "read",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Offset" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ListOk" }
        }
      },
      "post": {
        "tags": ["Smart Cards"],
        "summary": "Create a Smart Card",
        "x-permission": "cards:write",
        "x-rate-limit-cost": "write",
        "requestBody": { "$ref": "#/components/requestBodies/GenericJson" },
        "responses": {
          "201": { "$ref": "#/components/responses/CreatedItem" }
        }
      }
    },
    "/smart-cards/{id}": {
      "parameters": [
        { "$ref": "#/components/parameters/NumericId" }
      ],
      "patch": {
        "tags": ["Smart Cards"],
        "summary": "Update a Smart Card",
        "x-permission": "cards:write",
        "x-rate-limit-cost": "write",
        "requestBody": { "$ref": "#/components/requestBodies/GenericJson" },
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" }
        }
      },
      "delete": {
        "tags": ["Smart Cards"],
        "summary": "Delete a Smart Card",
        "x-permission": "cards:write",
        "x-rate-limit-cost": "write",
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" }
        }
      }
    },
    "/smart-cards/{id}/logo": {
      "post": {
        "tags": ["Smart Cards"],
        "summary": "Upload one Smart Card logo",
        "description": "Requires cards:write and uploads:write. Source image limit is 3 MB and the stored logo is compressed to small WebP.",
        "x-permission": "cards:write + uploads:write",
        "x-rate-limit-cost": "upload",
        "parameters": [
          { "$ref": "#/components/parameters/NumericId" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": { "$ref": "#/components/schemas/LogoUpload" }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" },
          "422": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/counters": {
      "get": {
        "tags": ["Counters"],
        "summary": "List counters and Insight Pixels",
        "x-permission": "counters:read",
        "x-rate-limit-cost": "read",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Offset" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ListOk" }
        }
      },
      "post": {
        "tags": ["Counters"],
        "summary": "Create a counter or Insight Pixel",
        "x-permission": "counters:write",
        "x-rate-limit-cost": "write",
        "requestBody": { "$ref": "#/components/requestBodies/GenericJson" },
        "responses": {
          "201": { "$ref": "#/components/responses/CreatedItem" }
        }
      }
    },
    "/counters/{id}": {
      "parameters": [
        { "$ref": "#/components/parameters/NumericId" }
      ],
      "patch": {
        "tags": ["Counters"],
        "summary": "Update a counter",
        "x-permission": "counters:write",
        "x-rate-limit-cost": "write",
        "requestBody": { "$ref": "#/components/requestBodies/GenericJson" },
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" }
        }
      },
      "delete": {
        "tags": ["Counters"],
        "summary": "Delete a counter",
        "x-permission": "counters:write",
        "x-rate-limit-cost": "write",
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" }
        }
      }
    },
    "/support-hub": {
      "get": {
        "tags": ["Support Hub"],
        "summary": "Read Support Hub settings",
        "x-permission": "support:read",
        "x-rate-limit-cost": "read",
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" }
        }
      },
      "put": {
        "tags": ["Support Hub"],
        "summary": "Create or update Support Hub settings",
        "x-permission": "support:write",
        "x-rate-limit-cost": "write",
        "requestBody": { "$ref": "#/components/requestBodies/GenericJson" },
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" },
          "201": { "$ref": "#/components/responses/CreatedItem" }
        }
      },
      "patch": {
        "tags": ["Support Hub"],
        "summary": "Update Support Hub settings",
        "x-permission": "support:write",
        "x-rate-limit-cost": "write",
        "requestBody": { "$ref": "#/components/requestBodies/GenericJson" },
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" }
        }
      }
    },
    "/capture/blocks": {
      "get": {
        "tags": ["Capture"],
        "summary": "List Reward Unlock blocks",
        "x-permission": "capture:read",
        "x-rate-limit-cost": "read",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Offset" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ListOk" }
        }
      },
      "post": {
        "tags": ["Capture"],
        "summary": "Create or update a Reward Unlock block",
        "description": "Uploads require capture:write plus uploads:write. Reward images keep the existing 5 MB source image rule; reward files keep the existing 2 MB file rule.",
        "x-permission": "capture:write",
        "x-rate-limit-cost": "write or upload",
        "requestBody": { "$ref": "#/components/requestBodies/GenericJson" },
        "responses": {
          "201": { "$ref": "#/components/responses/CreatedItem" },
          "422": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/capture/submissions": {
      "get": {
        "tags": ["Capture"],
        "summary": "List Reward Unlock submissions",
        "x-permission": "capture:read",
        "x-rate-limit-cost": "export",
        "parameters": [
          { "name": "context_type", "in": "query", "required": true, "schema": { "type": "string" } },
          { "name": "context_id", "in": "query", "required": true, "schema": { "type": "integer" } },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Offset" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ListOk" }
        }
      }
    },
    "/creator-drops": {
      "get": {
        "tags": ["Creator Drops"],
        "summary": "List Creator Drops",
        "x-permission": "drops:read",
        "x-rate-limit-cost": "read",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Offset" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ListOk" }
        }
      },
      "post": {
        "tags": ["Creator Drops"],
        "summary": "Create a Creator Drop",
        "x-permission": "drops:write",
        "x-rate-limit-cost": "write",
        "requestBody": { "$ref": "#/components/requestBodies/GenericJson" },
        "responses": {
          "201": { "$ref": "#/components/responses/CreatedItem" }
        }
      }
    },
    "/creator-drops/{id}": {
      "parameters": [
        { "$ref": "#/components/parameters/NumericId" }
      ],
      "patch": {
        "tags": ["Creator Drops"],
        "summary": "Update a Creator Drop",
        "x-permission": "drops:write",
        "x-rate-limit-cost": "write",
        "requestBody": { "$ref": "#/components/requestBodies/GenericJson" },
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" }
        }
      },
      "delete": {
        "tags": ["Creator Drops"],
        "summary": "Delete Creator Drop files",
        "x-permission": "drops:write",
        "x-rate-limit-cost": "write",
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" }
        }
      }
    },
    "/creator-drops/{id}/images": {
      "post": {
        "tags": ["Creator Drops"],
        "summary": "Upload one Creator Drop image",
        "description": "Requires drops:write and uploads:write. This endpoint accepts exactly one image per request and uses existing compression and plan storage limits.",
        "x-permission": "drops:write + uploads:write",
        "x-rate-limit-cost": "upload",
        "parameters": [
          { "$ref": "#/components/parameters/NumericId" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/ImageUpload"
              }
            }
          }
        },
        "responses": {
          "201": { "$ref": "#/components/responses/CreatedItem" },
          "422": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/ama": {
      "get": {
        "tags": ["AMA"],
        "summary": "Read AMA settings",
        "x-permission": "ama:read",
        "x-rate-limit-cost": "read",
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" }
        }
      },
      "post": {
        "tags": ["AMA"],
        "summary": "Create or update AMA settings",
        "x-permission": "ama:write",
        "x-rate-limit-cost": "write",
        "requestBody": { "$ref": "#/components/requestBodies/GenericJson" },
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" },
          "201": { "$ref": "#/components/responses/CreatedItem" }
        }
      }
    },
    "/ama/questions": {
      "get": {
        "tags": ["AMA"],
        "summary": "List AMA questions",
        "x-permission": "ama:read",
        "x-rate-limit-cost": "read",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Offset" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ListOk" }
        }
      }
    },
    "/ama/questions/{id}/answer": {
      "post": {
        "tags": ["AMA"],
        "summary": "Answer an AMA question",
        "x-permission": "ama:write",
        "x-rate-limit-cost": "write",
        "parameters": [
          { "$ref": "#/components/parameters/NumericId" }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AmaAnswer"
              }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/analytics/summary": {
      "get": {
        "tags": ["Analytics"],
        "summary": "Read a workspace analytics summary",
        "x-permission": "analytics:read",
        "x-rate-limit-cost": "analytics",
        "responses": {
          "200": {
            "description": "Workspace analytics summary returned",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" },
              "X-RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SummaryResponse"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks": {
      "get": {
        "tags": ["Webhooks"],
        "summary": "List webhook endpoints",
        "x-permission": "webhooks:write",
        "x-rate-limit-cost": "read",
        "parameters": [
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Offset" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ListOk" }
        }
      },
      "post": {
        "tags": ["Webhooks"],
        "summary": "Create a webhook endpoint",
        "description": "Webhook deliveries are queued and signed with X-Lynko-Signature. Delivery attempts are retried with backoff by cron. The public redirect and tracking routes only enqueue events and do not call webhook URLs directly.",
        "x-permission": "webhooks:write",
        "x-rate-limit-cost": "write",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/WebhookCreate" },
              "example": {
                "name": "CRM sync",
                "target_url": "https://example.com/webhooks/lynko",
                "events": ["link.clicked", "qr.scanned", "capture.submitted"]
              }
            }
          }
        },
        "responses": {
          "201": { "$ref": "#/components/responses/CreatedItem" },
          "422": { "$ref": "#/components/responses/BadRequest" }
        }
      }
    },
    "/webhooks/{id}": {
      "parameters": [
        { "$ref": "#/components/parameters/NumericId" }
      ],
      "get": {
        "tags": ["Webhooks"],
        "summary": "Read a webhook endpoint",
        "x-permission": "webhooks:write",
        "x-rate-limit-cost": "read",
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      },
      "patch": {
        "tags": ["Webhooks"],
        "summary": "Update a webhook endpoint",
        "x-permission": "webhooks:write",
        "x-rate-limit-cost": "write",
        "requestBody": { "$ref": "#/components/requestBodies/GenericJson" },
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": { "$ref": "#/components/responses/BadRequest" }
        }
      },
      "delete": {
        "tags": ["Webhooks"],
        "summary": "Delete a webhook endpoint",
        "x-permission": "webhooks:write",
        "x-rate-limit-cost": "write",
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/webhooks/{id}/test": {
      "post": {
        "tags": ["Webhooks"],
        "summary": "Queue a webhook test event",
        "x-permission": "webhooks:write",
        "x-rate-limit-cost": "webhook-test",
        "parameters": [
          { "$ref": "#/components/parameters/NumericId" }
        ],
        "responses": {
          "200": { "$ref": "#/components/responses/ItemOk" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "Lynko API key"
      },
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Recommended API key header. This is the most reliable option on shared hosts that do not pass Authorization to PHP."
      }
    },
    "headers": {
      "RateLimitLimit": {
        "description": "Effective request limit for the current rate window.",
        "schema": { "type": "integer" }
      },
      "RateLimitRemaining": {
        "description": "Requests remaining in the limiting rate window.",
        "schema": { "type": "integer" }
      },
      "RateLimitReset": {
        "description": "Unix timestamp when the limiting rate window resets.",
        "schema": { "type": "integer" }
      },
      "RateLimitPolicy": {
        "description": "Compact rate policy, for example 60;w=60.",
        "schema": { "type": "string" }
      },
      "RetryAfter": {
        "description": "Seconds to wait after a 429 response.",
        "schema": { "type": "integer" }
      }
    },
    "parameters": {
      "Limit": {
        "name": "limit",
        "in": "query",
        "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }
      },
      "Offset": {
        "name": "offset",
        "in": "query",
        "schema": { "type": "integer", "minimum": 0, "default": 0 }
      },
      "NumericId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": { "type": "integer", "minimum": 1 }
      },
      "ShortCode": {
        "name": "code",
        "in": "path",
        "required": true,
        "schema": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$" }
      },
      "QrCode": {
        "name": "code",
        "in": "path",
        "required": true,
        "schema": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,32}$" }
      },
      "Handle": {
        "name": "handle",
        "in": "path",
        "required": true,
        "schema": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,64}$" }
      }
    },
    "requestBodies": {
      "GenericJson": {
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      }
    },
    "responses": {
      "ItemOk": {
        "description": "Object response",
        "headers": {
          "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
          "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
          "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" },
          "X-RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ItemResponse" }
          }
        }
      },
      "CreatedItem": {
        "description": "Object created",
        "headers": {
          "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
          "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
          "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" },
          "X-RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ItemResponse" }
          }
        }
      },
      "ListOk": {
        "description": "List response",
        "headers": {
          "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
          "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
          "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" },
          "X-RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ListResponse" }
          }
        }
      },
      "BadRequest": {
        "description": "Validation or request error",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" },
            "example": {
              "ok": false,
              "error": "missing_bearer_token",
              "message": "Send an X-API-Key header. Authorization: Bearer API_KEY is also supported when your server forwards Authorization headers to PHP."
            }
          }
        }
      },
      "Forbidden": {
        "description": "Permission denied",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" },
            "example": {
              "ok": false,
              "error": "permission_denied",
              "message": "This API key does not have the required permission.",
              "required": "links:write"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "Feature unavailable until the required migration or service is ready",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded",
        "headers": {
          "X-RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
          "X-RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
          "X-RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" },
          "X-RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" },
          "Retry-After": { "$ref": "#/components/headers/RetryAfter" }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" },
            "example": {
              "ok": false,
              "error": "rate_limit_exceeded",
              "message": "Rate limit exceeded."
            }
          }
        }
      }
    },
    "schemas": {
      "ErrorResponse": {
        "type": "object",
        "required": ["ok", "error"],
        "properties": {
          "ok": { "type": "boolean", "enum": [false] },
          "error": { "type": "string" },
          "message": { "type": "string" }
        },
        "additionalProperties": true
      },
      "ItemResponse": {
        "type": "object",
        "required": ["ok", "item"],
        "properties": {
          "ok": { "type": "boolean", "enum": [true] },
          "item": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "ListResponse": {
        "type": "object",
        "required": ["ok", "items", "pagination"],
        "properties": {
          "ok": { "type": "boolean", "enum": [true] },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        },
        "additionalProperties": true
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "limit": { "type": "integer" },
          "offset": { "type": "integer" },
          "has_more": { "type": "boolean" },
          "next_offset": { "type": "integer", "nullable": true }
        }
      },
      "ShortlinkCreate": {
        "type": "object",
        "anyOf": [
          { "required": ["target_url"] },
          { "required": ["url"] }
        ],
        "properties": {
          "target_url": { "type": "string", "format": "uri" },
          "url": { "type": "string", "format": "uri", "description": "Alias for target_url." },
          "short_code": { "type": "string", "pattern": "^[A-Za-z0-9_-]{3,32}$" },
          "analytics": { "type": "boolean", "default": true }
        }
      },
      "QrCreate": {
        "type": "object",
        "required": ["target_url"],
        "properties": {
          "target_url": { "type": "string", "format": "uri" },
          "title": { "type": "string", "maxLength": 120 },
          "fg_color": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$" },
          "bg_color": { "type": "string", "pattern": "^#[0-9A-Fa-f]{6}$" },
          "ecl": { "type": "string", "enum": ["L", "M", "Q", "H"] },
          "size_px": { "type": "integer", "minimum": 256, "maximum": 1024 },
          "margin": { "type": "integer", "minimum": 1, "maximum": 8 }
        }
      },
      "QrCreateMultipart": {
        "allOf": [
          { "$ref": "#/components/schemas/QrCreate" },
          {
            "type": "object",
            "properties": {
              "qr_logo": { "type": "string", "format": "binary" }
            }
          }
        ]
      },
      "PollCreate": {
        "type": "object",
        "required": ["question", "options"],
        "properties": {
          "question": { "type": "string", "maxLength": 255 },
          "options": {
            "type": "array",
            "minItems": 2,
            "items": { "type": "string", "maxLength": 100 }
          },
          "duration_minutes": { "type": "integer", "minimum": 5 },
          "public_results": { "type": "boolean", "default": true }
        }
      },
      "LogoUpload": {
        "type": "object",
        "required": ["logo"],
        "properties": {
          "logo": { "type": "string", "format": "binary" }
        }
      },
      "ImageUpload": {
        "type": "object",
        "required": ["image"],
        "properties": {
          "image": { "type": "string", "format": "binary" }
        }
      },
      "AmaAnswer": {
        "type": "object",
        "required": ["answer"],
        "properties": {
          "answer": { "type": "string", "maxLength": 20000 },
          "is_public": { "type": "boolean", "default": true }
        }
      },
      "WebhookCreate": {
        "type": "object",
        "description": "Webhook targets must be public HTTPS URLs. Lynko rejects localhost, private or reserved IP targets, Lynko self-targets, URL credentials, fragments, and malformed hosts.",
        "required": ["target_url"],
        "properties": {
          "name": { "type": "string", "maxLength": 120 },
          "target_url": { "type": "string", "format": "uri", "pattern": "^https://" },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["link.clicked", "qr.scanned", "capture.submitted", "campaign.lead_created", "poll.voted", "billing.changed"]
            }
          }
        }
      },
      "SummaryResponse": {
        "type": "object",
        "required": ["ok", "summary"],
        "properties": {
          "ok": { "type": "boolean", "enum": [true] },
          "summary": {
            "type": "object",
            "properties": {
              "shortlinks": { "type": "integer" },
              "shortlink_clicks": { "type": "integer" },
              "qr_codes": { "type": "integer" },
              "qr_scans": { "type": "integer" },
              "link_pages": { "type": "integer" },
              "link_page_views": { "type": "integer" },
              "polls": { "type": "integer" },
              "campaign_kits": { "type": "integer" },
              "counters": { "type": "integer" },
              "creator_drops": { "type": "integer" }
            }
          }
        }
      }
    }
  },
  "x-permissions": {
    "account:read": "Read account email and subscription details",
    "links:read": "Read short links",
    "links:write": "Create and update short links",
    "qr:read": "Read QR codes",
    "qr:write": "Create and update QR codes",
    "trees:read": "Read link pages",
    "trees:write": "Create and update link pages",
    "polls:read": "Read polls",
    "polls:write": "Create and update polls",
    "ama:read": "Read AMA pages",
    "ama:write": "Manage AMA pages",
    "capture:read": "Read Reward Unlock data",
    "capture:write": "Manage Reward Unlock blocks",
    "campaigns:read": "Read Campaign Kits",
    "campaigns:write": "Create and update Campaign Kits",
    "cards:read": "Read Smart Cards",
    "cards:write": "Create and update Smart Cards",
    "counters:read": "Read counters and Insight Pixels",
    "counters:write": "Create and update counters and Insight Pixels",
    "support:read": "Read Support Hub",
    "support:write": "Manage Support Hub",
    "drops:read": "Read Creator Drops",
    "drops:write": "Create and update Creator Drops",
    "analytics:read": "Read analytics summaries",
    "webhooks:write": "Manage webhooks",
    "uploads:write": "Upload small API media"
  },
  "x-webhook-signatures": {
    "headers": ["X-Lynko-Event", "X-Lynko-Timestamp", "X-Lynko-Signature"],
    "signature_base": "timestamp + '.' + raw_request_body",
    "algorithm": "HMAC-SHA256",
    "note": "Current endpoints store a hash of the one-time secret. Verify by deriving sha256(raw webhook secret) as the HMAC key, then compare against X-Lynko-Signature after the sha256= prefix."
  }
}
