{
  "openapi": "3.1.0",
  "info": {
    "title": "igsub.me Free API",
    "version": "1.2.0",
    "description": "A 100% free (no fees, no paid plans, no API key), public, browser-only JSON API for growing YouTube channels (subscribers, likes, comments, comment likes) and, when enabled, Instagram / TikTok / X / Facebook posts. There is no API key: your web page calls the API directly with fetch() (CORS), the visitor signs in with their own Google account, and every order spends that visitor's free credits. It is the same API our Blogger theme and our own sites use, so you can build a complete site with it: home page with order form, channel page, video page, order tracking page and account page. Every order is protected by hCaptcha.\n\nRules:\n- Base URL is always https://igsub.me — every request goes there. All responses are JSON (UTF-8).\n- Send the header X-FS-Mode: token on every request and never send cookies (credentials: \"omit\").\n- Without sign-in a visitor can find a channel/video and send 100 free YouTube subscribers once per channel (hCaptcha required). Everything else — likes, comments, other platforms, larger quantities, account, credits, history — requires Google sign-in.\n- After sign-in send Authorization: Bearer <session_token> on every request. The token is valid 30 days.\n- Browser only: requests must come from a real web page (the browser sends Origin, Sec-Fetch-Mode: cors and an OPTIONS preflight). Server-side calls (PHP, cURL, Python, Node, bots) get 403 browser_required.\n- Use POST with Content-Type: application/json for request bodies; unknown body fields are rejected.\n- hCaptcha is required for every order (body field captcha_token) and for invite codes and channel credits (header X-FS-Captcha). Sign-in has no captcha. You need no captcha key: show https://igsub.me/captcha/frame.html in an iframe (it opens in the visitor's browser language; add ?hl=xx to force one), the visitor solves it there, and you receive the token via postMessage. One token = one call. Never put any secret in your page; none is needed.\n- Errors are { \"kod\": \"<code>\", \"error\": \"<English message>\", \"hata\": \"<same>\" } with a matching HTTP status. Translate on your side by kod.\n- The Accept-Language header (sent automatically by browsers) selects the language of YouTube titles/descriptions where available.\n- Accounts and credits are shared: a user who signs in on your site has the same account and balance as on igsub.me.\n\nCredits:\n- Without sign-in: 100 free YouTube subscribers once per channel (max 1 free order per hour per visitor). No credits needed.\n- Credits are free. They are not money and cannot be bought or withdrawn.\n- Every signed-in user gets free credits every hour (gunluk_hak, normally 1000) plus a welcome bonus on the first sign-in.\n- Each friend who signs in with the user's invite link/code gives +1000 credits. Invite link: https://igsub.me/?ref=<referans_kod> (or pass ref to /api/auth/device/start on your own site).\n- Each extra YouTube channel on the user's Google account adds +1000 credits per hour (POST /api/kredi/kanal-topla once per hour).\n- An order costs adet × kredi_carpani credits. If dispatch fails, credits are refunded automatically (iade).\n- Some services unlock only after a number of invites (min_referans).\n\nEarn credits with your site:\n- Put your own invite code on your site and earn +1000 credits for every new user who signs in there.\n- Get your code: sign in on igsub.me (or on your own site) and read referans_kod from GET /api/kredi — it is also shown on the account page.\n- Send it with every sign-in (no captcha needed): POST /api/auth/device/start?ref=YOURCODE. In the example page just set window.FS_REF = 'YOURCODE'.\n- If a visitor arrives with someone else's invite link (?ref=…), that link wins; otherwise your code is used.\n- A user can be invited only once (first invite counts). Limits against abuse: max 30 credited sign-ins per code per day and max 2 per network (IP) per day.\n- Spend the credits like any user: orders for your own channels on any of our sites. Credits are free and cannot be sold or withdrawn.\n- Your visitors also get their own invite code and link (https://igsub.me/?ref=THEIRCODE) to earn credits themselves.\n\nFree 100 subscribers (no sign-in):\n- Any visitor can send 100 free YouTube subscribers without signing in — no account, no credits, no API key.\n- Step 1: POST /api/resolve { \"q\": \"<channel link, @handle or name>\" } → channel_id (no Authorization header needed).\n- Step 2: show the hCaptcha frame (https://igsub.me/captcha/frame.html) and wait for the token.\n- Step 3: POST /api/order { \"tip\": \"sub\", \"hedef_kanal\": \"<channel_id>\", \"captcha_token\": \"<token>\", \"fingerprint\": \"<stable random id>\" } → order_id. Quantity is always 100.\n- Step 4: GET /api/order/{order_id} every 10 s to show live progress (no sign-in needed).\n- Limits: once per channel (lifetime), 1 free order per hour per visitor (IP + fingerprint), 1 free order per hour per channel, and a shared hourly cap for all external sites. When a limit is hit the API answers 401 giris_gerekli or 429 kanal_cooldown — then offer Google sign-in (free credits, every service).\n- Only tip \"sub\" works without sign-in; likes, comments, other platforms and quantities above 100 need sign-in.\n\nhCaptcha:\n- hCaptcha is required on: POST /api/order (body field captcha_token); POST /api/referans/uygula (header X-FS-Captcha); POST /api/kredi/kanal-topla (header X-FS-Captcha). Sign-in (POST /api/auth/device/start) does not need a captcha.\n- No captcha key is needed on your site. Show https://igsub.me/captcha/frame.html in an iframe (about 400x560 px). The visitor solves the check there (igsub.me domain, so our site key is valid everywhere).\n- When solved the frame calls window.parent.postMessage({ fsCaptcha: \"<token>\" }, \"*\") from origin https://igsub.me; { fsCaptchaExpired: true } if the token expired before use. Always check event.origin === \"https://igsub.me\".\n- A token is valid for one call only; get a new one for each order / invite / credit claim. Missing token → 400 captcha_yok, invalid/used/expired → 400 captcha_ret.\n- Frame query parameters: hl — Language of the check. Default (omitted or \"auto\"): the visitor's browser language, so nobody is forced into English. Force one with an hCaptcha language code: en, tr, es, pt, pt-BR, fr, de, it, nl, pl, ru, uk, ar, fa, hi, id, ms, th, vi, ja, ko, zh-CN, zh-TW, ro, cs, el, he, hu, sv, da, fi, no, bg, sr, hr, sk, sl, lt, lv, et and more. Unknown codes fall back to English. | theme — \"dark\" for a dark check that matches dark mode; default light.",
    "x-free-tier": {
      "without_sign_in": "100 YouTube subscribers once per channel (tip sub), hCaptcha required",
      "limits": [
        "once per channel",
        "1 per hour per visitor (IP + fingerprint)",
        "1 per hour per channel",
        "shared hourly cap for external sites"
      ],
      "endpoints": [
        "POST /api/resolve",
        "GET /api/channel/{channelId}",
        "GET /api/video/{videoId}",
        "POST /api/order",
        "GET /api/order/{order_id}"
      ],
      "signed_in": "all services with free hourly credits"
    },
    "x-updated": "2026-10-25",
    "x-changelog": [
      {
        "version": "1.2.0",
        "date": "2026-10-25",
        "changes": [
          "Sign-in (POST /api/auth/device/start) no longer needs hCaptcha. hCaptcha stays required for orders, invite codes and channel credits.",
          "Admin statistics per external site (requests, sign-ins, orders)."
        ]
      },
      {
        "version": "1.1.0",
        "date": "2026-10-25",
        "changes": [
          "hCaptcha is required for every order (captcha_token) and for sign-in start, invite codes and channel credits (X-FS-Captcha header). Solved in the igsub.me frame https://igsub.me/captcha/frame.html — no keys needed.",
          "Site owners can pass their own invite code (?ref=CODE / window.FS_REF) and earn +1000 credits per new user who signs in on their site.",
          "The example page (starter-kit.html) is now identical to our Blogger theme.",
          "Cross-site API is served only from https://igsub.me.",
          "Free without sign-in: 100 YouTube subscribers once per channel (resolve, channel, video, order and tracking work without sign-in; hCaptcha required).",
          "The hCaptcha frame opens in the visitor's browser language by default (?hl=auto); ?hl=xx forces a language."
        ]
      },
      {
        "version": "1.0.0",
        "date": "2026-10-25",
        "changes": [
          "First public release: no API key, browser-only CORS, Google sign-in, English errors (kod + error) and English order status."
        ]
      }
    ],
    "termsOfService": "https://igsub.me/legal/terms",
    "contact": {
      "name": "igsub.me",
      "url": "https://igsub.me/developers"
    },
    "x-pricing": "Free. No fees, no paid plans, no API key."
  },
  "servers": [
    {
      "url": "https://igsub.me",
      "description": "Production (browser CORS only)"
    }
  ],
  "externalDocs": {
    "url": "https://igsub.me/developers",
    "description": "Full HTML documentation"
  },
  "tags": [
    {
      "name": "Site & catalog",
      "description": "Public information. No sign-in needed."
    },
    {
      "name": "Authentication",
      "description": "Google sign-in with the YouTube TV device code flow. Produces the session token used everywhere else."
    },
    {
      "name": "Account & credits",
      "description": "The signed-in user, credit balance, credit history, invites and extra-channel credits."
    },
    {
      "name": "YouTube lookup",
      "description": "Find channels and videos from any link, @handle or name. Needed to get the channel_id / video_id that orders use."
    },
    {
      "name": "Orders",
      "description": "Create orders, follow live progress and list the user's history."
    },
    {
      "name": "Social proof",
      "description": "Recent completed deliveries (masked names) for a live activity feed."
    },
    {
      "name": "hCaptcha",
      "description": "hCaptcha is required on: POST /api/order (body field captcha_token); POST /api/referans/uygula (header X-FS-Captcha); POST /api/kredi/kanal-topla (header X-FS-Captcha). Sign-in (POST /api/auth/device/start) does not need a captcha. No captcha key is needed on your site. Show https://igsub.me/captcha/frame.html in an iframe (about 400x560 px). The visitor solves the check there (igsub.me domain, so our site key is valid everywhere). When solved the frame calls window.parent.postMessage({ fsCaptcha: \"<token>\" }, \"*\") from origin https://igsub.me; { fsCaptchaExpired: true } if the token expired before use. Always check event.origin === \"https://igsub.me\". A token is valid for one call only; get a new one for each order / invite / credit claim. Missing token → 400 captcha_yok, invalid/used/expired → 400 captcha_ret. Frame query parameters: hl — Language of the check. Default (omitted or \"auto\"): the visitor's browser language, so nobody is forced into English. Force one with an hCaptcha language code: en, tr, es, pt, pt-BR, fr, de, it, nl, pl, ru, uk, ar, fa, hi, id, ms, th, vi, ja, ko, zh-CN, zh-TW, ro, cs, el, he, hu, sv, da, fi, no, bg, sr, hr, sk, sl, lt, lv, et and more. Unknown codes fall back to English. | theme — \"dark\" for a dark check that matches dark mode; default light."
    }
  ],
  "x-hcaptcha": {
    "frame_url": "https://igsub.me/captcha/frame.html",
    "iframe_size": "400x560",
    "query": {
      "hl": "Language of the check. Default (omitted or \"auto\"): the visitor's browser language, so nobody is forced into English. Force one with an hCaptcha language code: en, tr, es, pt, pt-BR, fr, de, it, nl, pl, ru, uk, ar, fa, hi, id, ms, th, vi, ja, ko, zh-CN, zh-TW, ro, cs, el, he, hu, sv, da, fi, no, bg, sr, hr, sk, sl, lt, lv, et and more. Unknown codes fall back to English.",
      "theme": "\"dark\" for a dark check that matches dark mode; default light."
    },
    "message_origin": "https://igsub.me",
    "message_success": {
      "fsCaptcha": "<token>"
    },
    "message_expired": {
      "fsCaptchaExpired": true
    },
    "required_on": [
      {
        "endpoint": "POST /api/order",
        "send_as": "body field captcha_token"
      },
      {
        "endpoint": "POST /api/referans/uygula",
        "send_as": "header X-FS-Captcha"
      },
      {
        "endpoint": "POST /api/kredi/kanal-topla",
        "send_as": "header X-FS-Captcha"
      }
    ],
    "one_token_per_call": true,
    "errors": {
      "captcha_yok": "token missing (400)",
      "captcha_ret": "token invalid, expired or already used (400)"
    },
    "example_js": "window.addEventListener('message', e => { if (e.origin === 'https://igsub.me' && e.data && e.data.fsCaptcha) send(e.data.fsCaptcha); });"
  },
  "paths": {
    "/api/site": {
      "get": {
        "operationId": "site",
        "tags": [
          "Site & catalog"
        ],
        "summary": "Site information",
        "description": "Name, languages and public settings of the API host. Call it once on page load if you want to show the brand or language list.\n\nOther fields (analytics ids, verification tags) belong to igsub.me itself — ignore them.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "domain": {
                      "type": "string",
                      "description": "API host domain."
                    },
                    "ad": {
                      "type": "string",
                      "description": "Brand name."
                    },
                    "varsayilan_dil": {
                      "type": "string",
                      "description": "Default language code."
                    },
                    "diller": {
                      "type": "array",
                      "items": {},
                      "description": "Supported language codes."
                    },
                    "secili_dil": {
                      "type": "string",
                      "description": "Language picked for this request (Accept-Language)."
                    },
                    "telegram_kanal": {
                      "type": "string",
                      "description": "Telegram channel link."
                    },
                    "telegram_bot": {
                      "type": "string",
                      "description": "Telegram bot link."
                    }
                  }
                },
                "example": {
                  "domain": "igsub.me",
                  "ad": "iGSub",
                  "varsayilan_dil": "en",
                  "diller": [
                    "en",
                    "tr",
                    "es",
                    "pt",
                    "ar",
                    "fr",
                    "de",
                    "ru"
                  ],
                  "secili_dil": "en",
                  "telegram_kanal": "https://t.me/...",
                  "telegram_bot": "https://t.me/..."
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/link-servisler": {
      "get": {
        "operationId": "services",
        "tags": [
          "Site & catalog"
        ],
        "summary": "Service catalog",
        "description": "Every service with its limits and credit price. Use it to build the service picker, quantity limits and the cost preview. Hide services where aktif is false.\n\ncost in credits = quantity × kredi_carpani.\n\nmin_referans > 0 means the user needs that many invited friends before using the service (error referans_gerekli).",
        "parameters": [
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "servisler": {
                      "type": "array",
                      "items": {},
                      "description": "See the Service model."
                    }
                  }
                },
                "example": {
                  "servisler": [
                    {
                      "tip": "sub",
                      "platform": "youtube",
                      "tur": "sub",
                      "min_adet": 10,
                      "max_adet": 1000000,
                      "kredi_carpani": 1,
                      "min_referans": 0,
                      "aktif": true,
                      "sira": 0
                    },
                    {
                      "tip": "like",
                      "platform": "youtube",
                      "tur": "like",
                      "min_adet": 10,
                      "max_adet": 1000000,
                      "kredi_carpani": 50,
                      "min_referans": 3,
                      "aktif": true,
                      "sira": 6
                    },
                    {
                      "tip": "comment",
                      "platform": "youtube",
                      "tur": "comment",
                      "min_adet": 1,
                      "max_adet": 1000000,
                      "kredi_carpani": 10,
                      "min_referans": 100,
                      "aktif": true,
                      "sira": 7
                    },
                    {
                      "tip": "comment_like",
                      "platform": "youtube",
                      "tur": "comment_like",
                      "min_adet": 10,
                      "max_adet": 1000000,
                      "kredi_carpani": 15,
                      "min_referans": 5,
                      "aktif": true,
                      "sira": 8
                    },
                    {
                      "tip": "ig_views",
                      "platform": "instagram",
                      "tur": "views",
                      "min_adet": 100,
                      "max_adet": 2147483647,
                      "kredi_carpani": 3,
                      "min_referans": 0,
                      "aktif": false,
                      "sira": 1
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/device/start": {
      "post": {
        "operationId": "deviceStart",
        "tags": [
          "Authentication"
        ],
        "summary": "Start Google sign-in",
        "description": "Creates a one-time code. Show user_code to the user and send them to qr_url (opens youtube.com/qr/activate with the code pre-filled) or show qr_image to scan with a phone. Keep device_token in memory for polling.\n\nLimit: 12 starts per IP per 10 minutes.",
        "parameters": [
          {
            "name": "ref",
            "in": "query",
            "required": false,
            "description": "Invite code. Use YOUR code to earn +1000 credits for every new user who signs in on your site (or the visitor's ?ref= link if present). Applied when the sign-in completes; max 30 per code per day.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user_code": {
                      "type": "string",
                      "description": "Code the user enters (format XXX-XXX-XXX)."
                    },
                    "qr_url": {
                      "type": "string",
                      "description": "https://youtube.com/qr/activate/<code> — open in a new tab."
                    },
                    "qr_image": {
                      "type": "string",
                      "description": "PNG QR image URL of qr_url."
                    },
                    "verification_url": {
                      "type": "string",
                      "description": "Plain activation page (code typed manually)."
                    },
                    "interval": {
                      "type": "integer",
                      "description": "Seconds to wait between polls (use at least 3)."
                    },
                    "expires_in": {
                      "type": "integer",
                      "description": "Seconds until the code expires."
                    },
                    "device_token": {
                      "type": "string",
                      "description": "Opaque flow token; send it as X-FS-Device when polling."
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "user_code": "ABC-DEF-GHI",
                  "qr_url": "https://youtube.com/qr/activate/ABC-DEF-GHI",
                  "qr_image": "https://api.qrserver.com/v1/create-qr-code/?size=280x280&data=...",
                  "verification_url": "https://www.google.com/device",
                  "interval": 5,
                  "expires_in": 1800,
                  "device_token": "eyJ...signed"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Error (kod: device_hata)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {}
              },
              "example": {}
            }
          }
        }
      }
    },
    "/api/auth/device/poll": {
      "get": {
        "operationId": "devicePoll",
        "tags": [
          "Authentication"
        ],
        "summary": "Poll sign-in",
        "description": "Call every `interval` seconds until status is true, then save session_token (localStorage) and send it as Authorization: Bearer on every call. Always answers HTTP 200; read status/message.\n\nNew accounts receive a welcome credit bonus.\n\nA user who already has an account gets the same account and credits (accounts are shared with igsub.me and the blog).",
        "parameters": [
          {
            "name": "X-FS-Device",
            "in": "header",
            "required": true,
            "description": "device_token from /api/auth/device/start.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "boolean",
                      "description": "true = signed in."
                    },
                    "session_token": {
                      "type": "string",
                      "description": "Present when status is true. Valid for 30 days."
                    },
                    "message": {
                      "type": "string",
                      "description": "When status is false: \"not approved yet\" (keep polling), \"expired\" / \"no_flow\" (start again), \"only gmail\" (only @gmail.com accounts), other values = temporary, keep polling."
                    }
                  }
                },
                "example": {
                  "status": false,
                  "message": "not approved yet"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/me": {
      "get": {
        "operationId": "me",
        "tags": [
          "Authentication"
        ],
        "summary": "Current user",
        "description": "Validate the stored token on page load. 401 oturum_yok / kullanici_yok = token expired or deleted: clear it and show the sign-in button.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {}
                },
                "example": {
                  "ok": true,
                  "kullanici": {
                    "id": 156,
                    "email": "name@gmail.com",
                    "ad": "Name",
                    "avatar": "https://lh3.googleusercontent.com/...",
                    "son_giris": "2026-10-25T07:55:40Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Error (kod: oturum_yok, kullanici_yok)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/auth/logout": {
      "post": {
        "operationId": "logout",
        "tags": [
          "Authentication"
        ],
        "summary": "Sign out",
        "description": "Tokens are stateless: signing out = deleting session_token from your storage. Calling this endpoint is optional.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "Always true."
                    }
                  }
                },
                "example": {
                  "ok": true
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {}
              },
              "example": {}
            }
          }
        }
      }
    },
    "/api/kredi": {
      "get": {
        "operationId": "credit",
        "tags": [
          "Account & credits"
        ],
        "summary": "Credit balance",
        "description": "Spendable credits and invite info. Show kredi in the header and refresh it after every order.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {}
                },
                "example": {
                  "ok": true,
                  "kredi": {
                    "kredi": 1000,
                    "bonus_kredi": 0,
                    "referans_kod": "8E6WNYMQ",
                    "davet_sayisi": 0,
                    "kanal_sayisi": 0,
                    "gunluk_hak": 1000,
                    "sonraki_sifirlama": "2026-10-25T08:00:00Z"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Error (kod: oturum_yok)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/kredi/hareketler": {
      "get": {
        "operationId": "creditHistory",
        "tags": [
          "Account & credits"
        ],
        "summary": "Credit history",
        "description": "Credit movements, newest first.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Default 50.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Pagination offset.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "hareketler": {
                      "type": "array",
                      "items": {},
                      "description": "See the CreditTx model."
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "hareketler": [
                    {
                      "id": 991,
                      "tur": "siparis",
                      "miktar": -100,
                      "aciklama": "tip=sub adet=100x1",
                      "order_id": 3927,
                      "olusturma": "2026-10-24T18:05:43Z"
                    },
                    {
                      "id": 990,
                      "tur": "saatlik",
                      "miktar": 1000,
                      "aciklama": "hourly",
                      "olusturma": "2026-10-24T18:00:00Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Error (kod: oturum_yok)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/referans/uygula": {
      "post": {
        "operationId": "referral",
        "tags": [
          "Account & credits"
        ],
        "summary": "Apply an invite code",
        "description": "Manually apply a friend's invite code (the inviter gets +1000 credits). Usually not needed: pass ?ref= to device/start instead.\n\n**hCaptcha required** — get a token from the igsub.me frame (https://igsub.me/captcha/frame.html, see \"hCaptcha\" in the API description) and send it as header X-FS-Captcha.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XFSCaptcha"
          },
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "description": "true on success."
                    },
                    "bonus": {
                      "type": "integer",
                      "description": "Credits given to the inviter."
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "veren_id": 12,
                  "bonus": 1000
                }
              }
            }
          },
          "400": {
            "description": "Error (kod: captcha_yok, captcha_ret, kod_yok, referans_kendi)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error (kod: referans_gecersiz)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Error (kod: referans_var)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error (kod: referans_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-captcha-required": true,
        "x-captcha": {
          "frame": "https://igsub.me/captcha/frame.html",
          "send_as": "header X-FS-Captcha",
          "one_token_per_call": true
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kod": {
                    "type": "string",
                    "description": "Invite code.",
                    "example": "AB12CD34"
                  }
                },
                "required": [
                  "kod"
                ]
              },
              "example": {
                "kod": "AB12CD34"
              }
            }
          }
        }
      }
    },
    "/api/kredi/kanal-topla": {
      "post": {
        "operationId": "channelCredits",
        "tags": [
          "Account & credits"
        ],
        "summary": "Claim extra-channel credits",
        "description": "Counts the extra YouTube channels on the user's Google account; every channel adds +1000 credits per hour. Once per hour (when called earlier you get beklemede=true and kalan_saniye).\n\n**hCaptcha required** — get a token from the igsub.me frame (https://igsub.me/captcha/frame.html, see \"hCaptcha\" in the API description) and send it as header X-FS-Captcha.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XFSCaptcha"
          },
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "kanal_sayisi": {
                      "type": "integer",
                      "description": "Extra channels found."
                    },
                    "saatlik_kazanc": {
                      "type": "integer",
                      "description": "Credits per hour from channels."
                    },
                    "kredi": {
                      "type": "integer",
                      "description": "New balance."
                    },
                    "beklemede": {
                      "type": "boolean",
                      "description": "true = called too early."
                    },
                    "kalan_saniye": {
                      "type": "integer",
                      "description": "Seconds until it can be called again."
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "kanal_sayisi": 3,
                  "saatlik_kazanc": 3000,
                  "kredi": 4000
                }
              }
            }
          },
          "400": {
            "description": "Error (kod: captcha_yok, captcha_ret)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error (kod: oturum_yok)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Error (kod: kanal_alinamadi)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-captcha-required": true,
        "x-captcha": {
          "frame": "https://igsub.me/captcha/frame.html",
          "send_as": "header X-FS-Captcha",
          "one_token_per_call": true
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {}
              },
              "example": {}
            }
          }
        }
      }
    },
    "/api/resolve": {
      "post": {
        "operationId": "resolve",
        "tags": [
          "YouTube lookup"
        ],
        "summary": "Find a channel or video",
        "description": "Accepts anything a user may paste: channel URL, @handle, /c/ or /user/ URL, channel name, video URL (watch, youtu.be, shorts) or a bare video id. Returns a channel (tip = \"kanal\") with its latest videos and similar channels, or a video (tip = \"video\").\n\nLimit: 40 per minute per IP.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "tip": {
                      "type": "string",
                      "description": "What was found."
                    },
                    "channel_id": {
                      "type": "string",
                      "description": "UC… id — use as hedef_kanal in orders."
                    },
                    "ad": {
                      "type": "string",
                      "description": "Channel name."
                    },
                    "handle": {
                      "type": "string",
                      "description": "Handle without @ (channel only, may be empty)."
                    },
                    "avatar": {
                      "type": "string",
                      "description": "Channel avatar (channel only)."
                    },
                    "abone_sayi": {
                      "type": "integer",
                      "description": "Subscriber count (channel only)."
                    },
                    "video_sayi": {
                      "type": "integer",
                      "description": "Video count (channel only)."
                    },
                    "aciklama": {
                      "type": "string",
                      "description": "Channel description (channel only)."
                    },
                    "videolar": {
                      "type": "array",
                      "items": {},
                      "description": "Latest videos (channel only). id = video id."
                    },
                    "alternatifler": {
                      "type": "array",
                      "items": {},
                      "description": "Similar channels when the input was a name/handle — show \"Not this channel?\"."
                    },
                    "video_id": {
                      "type": "string",
                      "description": "Video id (video only)."
                    },
                    "video": {
                      "type": "object",
                      "description": "Video only: {id, baslik, aciklama, kapak, goruntulenme, sure_sn, yazar}."
                    }
                  }
                },
                "example": {
                  "tip": "kanal",
                  "channel_id": "UCX6OQ3DkcsbYNE6H8uQQuVA",
                  "ad": "MrBeast",
                  "handle": "mrbeast",
                  "avatar": "https://yt3.ggpht.com/...",
                  "abone_sayi": 523000000,
                  "video_sayi": 900,
                  "aciklama": "...",
                  "yontem": "handle",
                  "videolar": [
                    {
                      "id": "dQw4w9WgXcQ",
                      "baslik": "Video title",
                      "kapak": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg",
                      "bilgi": "12M views"
                    }
                  ],
                  "alternatifler": [
                    {
                      "channel_id": "UC...",
                      "ad": "MrBeast Gaming",
                      "avatar": "...",
                      "abone_sayi": 45000000
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Error (kod: bos_girdi)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error (kod: giris_gerekli)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Error (kod: cozulemedi)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "q": {
                    "type": "string",
                    "description": "User input.",
                    "example": "https://www.youtube.com/@MrBeast"
                  }
                },
                "required": [
                  "q"
                ]
              },
              "example": {
                "q": "https://www.youtube.com/@MrBeast"
              }
            }
          }
        }
      }
    },
    "/api/search": {
      "get": {
        "operationId": "search",
        "tags": [
          "YouTube lookup"
        ],
        "summary": "Search channels",
        "description": "Channel search by name (max 12). Use for an autocomplete or a \"pick your channel\" list.",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Search text.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "kanallar": {
                      "type": "array",
                      "items": {},
                      "description": "Matching channels."
                    }
                  }
                },
                "example": {
                  "kanallar": [
                    {
                      "channel_id": "UC...",
                      "ad": "Cooking Channel",
                      "avatar": "https://...",
                      "abone_sayi": 120000
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Error (kod: bos_girdi)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error (kod: giris_gerekli)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/channel/{channelId}": {
      "get": {
        "operationId": "channel",
        "tags": [
          "YouTube lookup"
        ],
        "summary": "Channel page data",
        "description": "Full channel card plus the first 30 videos. Use it for a channel page (e.g. #/channel/UC...).",
        "parameters": [
          {
            "name": "channelId",
            "in": "path",
            "required": true,
            "description": "UC… channel id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "kanal": {
                      "type": "object",
                      "description": "See the Channel model."
                    },
                    "videolar": {
                      "type": "array",
                      "items": {},
                      "description": "See the Video model."
                    }
                  }
                },
                "example": {
                  "kanal": {
                    "channel_id": "UCX6OQ3DkcsbYNE6H8uQQuVA",
                    "handle": "mrbeast",
                    "ad": "MrBeast",
                    "aciklama": "...",
                    "avatar": "...",
                    "banner": "",
                    "abone_sayi": 523000000,
                    "video_sayi": 900,
                    "ulke": "",
                    "kategori": ""
                  },
                  "videolar": [
                    {
                      "video_id": "...",
                      "channel_id": "UC...",
                      "baslik": "...",
                      "kapak": "...",
                      "goruntulenme": 1000000,
                      "sure_sn": 900,
                      "sira": 1
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Error (kod: gecersiz_kanal)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error (kod: giris_gerekli)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error (kod: kanal_yok)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/channel/{channelId}/videos": {
      "get": {
        "operationId": "channelVideos",
        "tags": [
          "YouTube lookup"
        ],
        "summary": "More channel videos / shorts",
        "description": "Paginated videos. Default tab uses limit/offset. For Shorts or live streams pass sekme=shorts|streams and page with the returned devam token (continuation).",
        "parameters": [
          {
            "name": "channelId",
            "in": "path",
            "required": true,
            "description": "UC… channel id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "1–100, default 30 (videos tab).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Offset (videos tab).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "sekme",
            "in": "query",
            "required": false,
            "description": "Tab. Default videos.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "continuation",
            "in": "query",
            "required": false,
            "description": "Next-page token (devam) for shorts/streams.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "videolar": {
                      "type": "array",
                      "items": {},
                      "description": "Videos (shorts/streams return {video_id, channel_id, baslik, kapak})."
                    },
                    "devam": {
                      "type": "string",
                      "description": "Next page token for shorts/streams (\"\" = end)."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error (kod: gecersiz_kanal, sekme_hata)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error (kod: giris_gerekli)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/video/{videoId}": {
      "get": {
        "operationId": "video",
        "tags": [
          "YouTube lookup"
        ],
        "summary": "Video page data",
        "description": "Video details plus its channel card. Use it for a video page with like/comment ordering.",
        "parameters": [
          {
            "name": "videoId",
            "in": "path",
            "required": true,
            "description": "11-character video id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "video": {
                      "type": "object",
                      "description": "{video_id, baslik, aciklama, channel_id, kapak, goruntulenme, sure_sn, etiketler[], yazar}"
                    },
                    "kanal": {
                      "type": "object",
                      "description": "Channel of the video."
                    }
                  }
                },
                "example": {
                  "video": {
                    "video_id": "dQw4w9WgXcQ",
                    "baslik": "Title",
                    "aciklama": "...",
                    "channel_id": "UC...",
                    "kapak": "https://i.ytimg.com/vi/dQw4w9WgXcQ/hqdefault.jpg",
                    "goruntulenme": 1500000000,
                    "sure_sn": 213,
                    "etiketler": [
                      "music"
                    ],
                    "yazar": "Channel"
                  },
                  "kanal": {
                    "channel_id": "UC...",
                    "ad": "Channel",
                    "avatar": "...",
                    "abone_sayi": 4000000
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error (kod: gecersiz_video)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error (kod: giris_gerekli)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error (kod: video_yok)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/order": {
      "post": {
        "operationId": "orderCreate",
        "tags": [
          "Orders"
        ],
        "summary": "Create an order",
        "description": "Without sign-in: 100 free YouTube subscribers (tip sub only) once per channel, max 1 free order per hour per visitor, hCaptcha required. Signed in: spends credits (quantity × kredi_carpani) and starts delivery immediately. YouTube services need hedef_kanal (channel_id from /api/resolve); like and comment also need hedef_video. Instagram / TikTok / X / Facebook services need the full post URL in hedef_video and no hedef_kanal.\n\nLimit: 30 orders per hour per user (external sites) · 20 per hour per IP.\n\nEvery order needs a fresh hCaptcha token (one token = one order).\n\nRequest body must contain only these fields (unknown fields → 400 gecersiz_govde).\n\nRefresh /api/kredi after an order.\n\n**hCaptcha required** — get a token from the igsub.me frame (https://igsub.me/captcha/frame.html, see \"hCaptcha\" in the API description) and send it as body field captcha_token.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "order_id": {
                      "type": "string",
                      "description": "Unguessable order token. Keep it: it is the key for tracking (share links like #/order/<order_id>)."
                    },
                    "status": {
                      "type": "object",
                      "description": "Usually \"queued\"; \"failed\" if dispatch failed (credits refunded automatically)."
                    },
                    "durum": {
                      "type": "string",
                      "description": "Same status, internal name."
                    },
                    "adet": {
                      "type": "integer",
                      "description": "Final quantity after clamping."
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "order_id": "c43a7ec92aef79f3e7f96c1b55125d44",
                  "durum": "kuyrukta",
                  "status": "queued",
                  "adet": 100
                }
              }
            }
          },
          "400": {
            "description": "Error (kod: captcha_yok, captcha_ret, gecersiz_tip, hedef_yok, link_gecersiz, servis_yok, gecersiz_govde)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error (kod: giris_gerekli)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Error (kod: kredi_yok, min_kredi)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Error (kod: referans_gerekli)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Error (kod: kanal_cooldown, rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-captcha-required": true,
        "x-captcha": {
          "frame": "https://igsub.me/captcha/frame.html",
          "send_as": "body field captcha_token",
          "one_token_per_call": true
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tip": {
                    "type": "string",
                    "description": "Service type from /api/link-servisler: sub, like, comment, comment_like (YouTube) · ig_views, ig_likes, tiktok_views, x_views, fb_likes (only when aktif).",
                    "example": "sub"
                  },
                  "hedef_kanal": {
                    "type": "string",
                    "description": "YouTube channel_id (UC…). Required for YouTube services.",
                    "example": "UCX6OQ3DkcsbYNE6H8uQQuVA"
                  },
                  "hedef_video": {
                    "type": "string",
                    "description": "YouTube video id (like, comment, comment_like) or full post URL (Instagram/TikTok/X/Facebook).",
                    "example": "dQw4w9WgXcQ"
                  },
                  "adet": {
                    "type": "integer",
                    "description": "Quantity. Signed in: clamped to the service min/max and to what the balance can pay (omit = 100). Without sign-in: always 100 (omit it).",
                    "example": "100"
                  },
                  "captcha_token": {
                    "type": "string",
                    "description": "hCaptcha token, required for every order. Get it from the igsub.me captcha frame (see \"hCaptcha\" section): open https://igsub.me/captcha/frame.html in an iframe and read the token from the postMessage { fsCaptcha }. Single use."
                  },
                  "fingerprint": {
                    "type": "string",
                    "description": "Stable device id (random string kept in localStorage). Recommended without sign-in: the free quota is counted per IP + fingerprint."
                  }
                },
                "required": [
                  "tip",
                  "captcha_token"
                ]
              },
              "example": {
                "tip": "sub",
                "hedef_kanal": "UCX6OQ3DkcsbYNE6H8uQQuVA",
                "adet": 100,
                "captcha_token": "P1_eyJ0eXAi..."
              }
            }
          }
        }
      }
    },
    "/api/order/{order_id}": {
      "get": {
        "operationId": "orderStatus",
        "tags": [
          "Orders"
        ],
        "summary": "Order progress",
        "description": "Live progress, synced with the delivery engine on every call. Poll every 8–15 seconds while the status is not final. Progress = biten_sayac / adet.",
        "parameters": [
          {
            "name": "order_id",
            "in": "path",
            "required": true,
            "description": "Token from /api/order.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "order_id": {
                      "type": "string",
                      "description": "Order token."
                    },
                    "tip": {
                      "type": "string",
                      "description": "Service type."
                    },
                    "status": {
                      "type": "object",
                      "description": "pending | queued | in_progress | completed | canceled | failed."
                    },
                    "adet": {
                      "type": "integer",
                      "description": "Ordered quantity."
                    },
                    "baslangic_sayac": {
                      "type": "integer",
                      "description": "Counter (subscribers/likes) when delivery started; 0 until known."
                    },
                    "biten_sayac": {
                      "type": "integer",
                      "description": "Units delivered so far."
                    },
                    "hedef_kanal": {
                      "type": "string",
                      "description": "Target channel."
                    },
                    "hedef_video": {
                      "type": "string",
                      "description": "Target video / link."
                    },
                    "olusturma": {
                      "type": "string",
                      "format": "date-time",
                      "description": "Created at."
                    },
                    "biten": {
                      "type": "string",
                      "format": "date-time",
                      "description": "Finished at."
                    }
                  }
                },
                "example": {
                  "order_id": "c43a7ec92aef79f3e7f96c1b55125d44",
                  "tip": "sub",
                  "durum": "calisiyor",
                  "status": "in_progress",
                  "adet": 100,
                  "baslangic_sayac": 18300,
                  "biten_sayac": 40,
                  "hedef_kanal": "UCKtuGDfMjgNe5M9Eu_XsdRw",
                  "hedef_video": "",
                  "olusturma": "2026-10-24T18:05:43Z",
                  "biten": null
                }
              }
            }
          },
          "401": {
            "description": "Error (kod: giris_gerekli)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error (kod: siparis_yok)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/order/{order_id}/events": {
      "get": {
        "operationId": "orderEvents",
        "tags": [
          "Orders"
        ],
        "summary": "Order timeline",
        "description": "Step-by-step events for a timeline view.",
        "parameters": [
          {
            "name": "order_id",
            "in": "path",
            "required": true,
            "description": "Order token.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [
          {},
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "olaylar": {
                      "type": "array",
                      "items": {},
                      "description": "See the OrderEvent model."
                    }
                  }
                },
                "example": {
                  "order_id": "c43a7ec9...",
                  "olaylar": [
                    {
                      "id": 1,
                      "olay": "olusturuldu",
                      "detay": "tip=sub adet=100",
                      "ts": "2026-10-24T18:05:43Z"
                    },
                    {
                      "id": 2,
                      "olay": "kuyruga_alindi",
                      "detay": "ref=...",
                      "ts": "2026-10-24T18:05:44Z"
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error (kod: siparis_yok)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/siparislerim": {
      "get": {
        "operationId": "myOrders",
        "tags": [
          "Orders"
        ],
        "summary": "My orders",
        "description": "The signed-in user's orders on every site, newest first, with the latest event.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Default 50.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Pagination offset.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "siparisler": {
                      "type": "array",
                      "items": {},
                      "description": "See the OrderSummary model. Link each row with public_token."
                    }
                  }
                },
                "example": {
                  "ok": true,
                  "siparisler": [
                    {
                      "id": 3927,
                      "public_token": "c43a7ec9...",
                      "platform": "youtube",
                      "tip": "sub",
                      "hedef_kanal": "UC...",
                      "hedef_video": "",
                      "adet": 1000,
                      "durum": "tamamlandi",
                      "status": "completed",
                      "baslangic_sayac": 18300,
                      "biten_sayac": 1000,
                      "son_olay": "kuyruga_alindi",
                      "olusturma": "2026-10-24T18:05:43Z",
                      "biten": "2026-10-24T18:41:02Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Error (kod: oturum_yok)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/siparislerim/{id}": {
      "get": {
        "operationId": "myOrder",
        "tags": [
          "Orders"
        ],
        "summary": "One of my orders",
        "description": "Summary plus full event list of one order owned by the user (numeric id from /api/siparislerim).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Numeric order id.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "siparis": {
                      "type": "object",
                      "description": "Order summary."
                    },
                    "olaylar": {
                      "type": "array",
                      "items": {},
                      "description": "Events."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Error (kod: gecersiz_id)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Error (kod: oturum_yok)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Error (kod: siparis_yok)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/son-siparisler": {
      "get": {
        "operationId": "recent",
        "tags": [
          "Social proof"
        ],
        "summary": "Recent deliveries",
        "description": "Latest completed orders across all sites for a \"just delivered\" ticker. Names are masked server-side; no e-mail, channel or IP is returned. Cached 30 seconds.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XFSMode"
          }
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "siparisler": {
                      "type": "array",
                      "items": {},
                      "description": "ad = masked name (\"\" = guest), saniye_once = seconds ago."
                    }
                  }
                },
                "example": {
                  "siparisler": [
                    {
                      "tip": "sub",
                      "adet": 100,
                      "ad": "Al***",
                      "saniye_once": 42
                    },
                    {
                      "tip": "like",
                      "adet": 50,
                      "ad": "",
                      "saniye_once": 95
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "Forbidden (kod: browser_required — not a real browser request; not_public)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Too many requests (kod: rate_limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "session_token from GET /api/auth/device/poll (valid 30 days)."
      }
    },
    "parameters": {
      "XFSCaptcha": {
        "name": "X-FS-Captcha",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string"
        },
        "description": "hCaptcha token from https://igsub.me/captcha/frame.html (postMessage { fsCaptcha }). One token per call."
      },
      "XFSMode": {
        "name": "X-FS-Mode",
        "in": "header",
        "required": true,
        "schema": {
          "type": "string",
          "enum": [
            "token"
          ]
        },
        "description": "Always \"token\" (cookie-less cross-site mode)."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "kod": {
            "type": "string",
            "description": "Language-neutral error code.",
            "enum": [
              "giris_gerekli",
              "kanal_cooldown",
              "oturum_yok",
              "kullanici_yok",
              "browser_required",
              "not_public",
              "captcha_yok",
              "captcha_ret",
              "kredi_yok",
              "min_kredi",
              "referans_gerekli",
              "rate_limit",
              "gecersiz_govde",
              "gecersiz_tip",
              "hedef_yok",
              "link_gecersiz",
              "servis_yok",
              "bos_girdi",
              "cozulemedi",
              "kanal_yok",
              "video_yok",
              "siparis_yok",
              "referans_gecersiz",
              "referans_kendi",
              "referans_var",
              "referans_limit",
              "kanal_alinamadi",
              "device_hata",
              "db_hata"
            ]
          },
          "error": {
            "type": "string",
            "description": "English message."
          },
          "hata": {
            "type": "string",
            "description": "Same message (legacy field)."
          }
        },
        "required": [
          "kod"
        ]
      },
      "OrderStatus": {
        "type": "string",
        "enum": [
          "pending",
          "queued",
          "in_progress",
          "completed",
          "canceled",
          "failed"
        ],
        "description": "pending: Created, not yet sent to delivery. queued: Accepted by the delivery engine, waiting to start. in_progress: Delivering. biten_sayac grows. completed: Finished (final). canceled: Canceled (final). failed: Could not be dispatched; credits refunded (final)."
      },
      "Service": {
        "type": "object",
        "properties": {
          "tip": {
            "type": "string",
            "description": "Service type used in orders."
          },
          "platform": {
            "type": "string",
            "description": "youtube | instagram | tiktok | x | facebook"
          },
          "tur": {
            "type": "string",
            "description": "sub | like | comment | comment_like | views | likes"
          },
          "min_adet": {
            "type": "integer",
            "description": "Minimum quantity."
          },
          "max_adet": {
            "type": "integer",
            "description": "Maximum quantity per order."
          },
          "kredi_carpani": {
            "type": "integer",
            "description": "Credits per unit."
          },
          "min_referans": {
            "type": "integer",
            "description": "Invites needed to unlock (0 = open)."
          },
          "aktif": {
            "type": "boolean",
            "description": "false = hide it."
          },
          "sira": {
            "type": "integer",
            "description": "Display order."
          }
        },
        "description": "Item of /api/link-servisler."
      },
      "Channel": {
        "type": "object",
        "properties": {
          "channel_id": {
            "type": "string",
            "description": "UC… id."
          },
          "handle": {
            "type": "string",
            "description": "Handle without @."
          },
          "ad": {
            "type": "string",
            "description": "Name."
          },
          "aciklama": {
            "type": "string",
            "description": "Description."
          },
          "avatar": {
            "type": "string",
            "description": "Avatar URL."
          },
          "banner": {
            "type": "string",
            "description": "Banner URL (may be empty)."
          },
          "abone_sayi": {
            "type": "integer",
            "description": "Subscribers."
          },
          "video_sayi": {
            "type": "integer",
            "description": "Videos."
          }
        },
        "description": "YouTube channel card."
      },
      "Video": {
        "type": "object",
        "properties": {
          "video_id": {
            "type": "string",
            "description": "Video id."
          },
          "channel_id": {
            "type": "string",
            "description": "Channel id."
          },
          "baslik": {
            "type": "string",
            "description": "Title."
          },
          "aciklama": {
            "type": "string",
            "description": "Description."
          },
          "kapak": {
            "type": "string",
            "description": "Thumbnail URL (fallback: https://i.ytimg.com/vi/<id>/mqdefault.jpg)."
          },
          "goruntulenme": {
            "type": "integer",
            "description": "Views."
          },
          "sure_sn": {
            "type": "integer",
            "description": "Duration in seconds."
          },
          "etiketler": {
            "type": "array",
            "items": {},
            "description": "Tags."
          }
        },
        "description": "Video card in channel lists."
      },
      "OrderSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Numeric id (for /api/siparislerim/{id})."
          },
          "public_token": {
            "type": "string",
            "description": "Order token (for /api/order/{order_id})."
          },
          "platform": {
            "type": "string",
            "description": "Platform."
          },
          "tip": {
            "type": "string",
            "description": "Service type."
          },
          "hedef_kanal": {
            "type": "string",
            "description": "Target channel."
          },
          "hedef_video": {
            "type": "string",
            "description": "Target video / link."
          },
          "adet": {
            "type": "integer",
            "description": "Quantity."
          },
          "status": {
            "type": "object",
            "description": "English status."
          },
          "baslangic_sayac": {
            "type": "integer",
            "description": "Start count."
          },
          "biten_sayac": {
            "type": "integer",
            "description": "Delivered."
          },
          "son_olay": {
            "type": "string",
            "description": "Latest event name."
          },
          "olusturma": {
            "type": "string",
            "format": "date-time",
            "description": "Created."
          },
          "biten": {
            "type": "string",
            "format": "date-time",
            "description": "Finished (omitted while running)."
          }
        },
        "description": "Row of /api/siparislerim."
      },
      "OrderEvent": {
        "type": "object",
        "properties": {
          "olay": {
            "type": "string",
            "description": "olusturuldu (created) · kuyruga_alindi (queued for delivery) · teslimat_hata (dispatch error) · kredi_iade (credits refunded) · otomatik_sonlandir (closed automatically)"
          },
          "detay": {
            "type": "string",
            "description": "Free text detail."
          },
          "ts": {
            "type": "string",
            "format": "date-time",
            "description": "Time."
          }
        },
        "description": "Timeline item."
      },
      "CreditTx": {
        "type": "object",
        "properties": {
          "tur": {
            "type": "string",
            "description": "saatlik (hourly free credits) · hosgeldin (welcome bonus) · referans_bonus (invite bonus) · siparis (order, negative) · iade (refund)"
          },
          "miktar": {
            "type": "integer",
            "description": "+ added / − spent."
          },
          "aciklama": {
            "type": "string",
            "description": "Detail."
          },
          "order_id": {
            "type": "integer",
            "description": "Related order (optional)."
          },
          "olusturma": {
            "type": "string",
            "format": "date-time",
            "description": "Time."
          }
        },
        "description": "Credit history row."
      }
    }
  }
}