{
  "title": "Numbers API",
  "audience": "cursor-agent",
  "public": true,
  "auth_required_to_read": false,
  "origin": "https://onlygirls.click",
  "swagger_disabled": true,
  "docs": {
    "html": "https://onlygirls.click/docs",
    "markdown": "https://onlygirls.click/docs.md",
    "json": "https://onlygirls.click/docs.json",
    "text": "https://onlygirls.click/llms.txt"
  },
  "rules": [
    "Follow this contract exactly. Do not invent endpoints, parameters, or keys.",
    "Never invent or guess an API key. The human must provide nk_...",
    "Do not buy real numbers while testing. Use country=ger on getNumber (NO_NUMBERS). Use unknown 24-hex ids for getStatus/setStatus.",
    "If you accidentally receive ACCESS_NUMBER, cancel immediately with setStatus status=-1.",
    "After getNumber/getNumberSelected, store the local 24-hex activation_id. Never send a partner/upstream id.",
    "getNumberSelected success is JSON. Every other SMS success/error is text/plain.",
    "country is ISO 3166-1 alpha-2 lowercase (it, gb, tj). ger is a safe no-number probe.",
    "operator is the monitor display name. Empty or any means any operator.",
    "Do not call partner hosts. Only this origin.",
    "Do not use Authorization: Bearer. Only x-api-key or api_key.",
    "SMS methods always return HTTP 200, including BAD_KEY."
  ],
  "auth": {
    "sms": {
      "type": "api_key",
      "prefix": "nk_",
      "pass_one_of": [
        { "in": "header", "name": "x-api-key" },
        { "in": "query", "name": "api_key" },
        { "in": "body", "name": "api_key" }
      ],
      "resolution_order": ["header x-api-key", "query api_key", "body api_key"],
      "invalid_response": {
        "http_status": 200,
        "content_type": "text/plain",
        "body": "BAD_KEY"
      },
      "notes": "Keys are created in /admin. This docs page never returns keys."
    },
    "monitor": {
      "type": "session_cookie",
      "note": "API keys do not work on /api/state, /api/selected, /admin, or the UI."
    }
  },
  "entrypoints": {
    "handler": [
      "https://onlygirls.click/stubs/handler_api.php",
      "https://onlygirls.click/handler_api.php",
      "https://onlygirls.click/api/handler"
    ],
    "getNumberSelected_shortcut": "https://onlygirls.click/api/getNumberSelected",
    "methods_allowed": ["GET", "POST"],
    "param_merge": "query + JSON + urlencoded + multipart; non-empty query wins over body"
  },
  "methods": {
    "getNumber": {
      "action": "getNumber",
      "description": "Issue a phone number for a service + country + operator.",
      "params": {
        "action": { "required": true, "value": "getNumber" },
        "service": { "required": true, "example": "ig" },
        "country": { "required": true, "format": "iso3166-1-alpha-2-lowercase", "safe_probe": "ger" },
        "operator": { "required": false, "default": "any", "note": "display name from the monitor table" },
        "api_key": { "required": "unless x-api-key header" }
      },
      "success": {
        "http_status": 200,
        "content_type": "text/plain",
        "body": "ACCESS_NUMBER:<activation_id>:<phone>",
        "activation_id": "24 lowercase hex"
      },
      "errors": ["NO_NUMBERS", "BAD_KEY"],
      "safe_test": "GET /stubs/handler_api.php?api_key=KEY&action=getNumber&service=ig&country=ger → NO_NUMBERS"
    },
    "getNumberSelected": {
      "action": "getNumberSelected",
      "description": "Pick one random checked operator from the monitor, then buy a number for that country/operator. Preferred when the human already selected rows.",
      "shortcut": "GET/POST /api/getNumberSelected (no action needed)",
      "params": {
        "action": { "required": "on handler URLs only", "value": "getNumberSelected" },
        "service": { "required": false, "note": "limit to one service code; omit = all codes with checked rows" },
        "api_key": { "required": "unless x-api-key header" }
      },
      "success": {
        "http_status": 200,
        "content_type": "application/json",
        "fields": {
          "issue_id": "UUID, monitor stats only, do not send back",
          "issued_at": "ISO-8601 UTC",
          "service": "service code actually used",
          "id": "monitor row id country_id|operator|index",
          "country_id": "number",
          "code": "ISO2 uppercase in this JSON",
          "country_name": "English name",
          "operator": "display name sent to partner as operator=",
          "rate": "number, partner rate at issue time",
          "activation_id": "24-hex local id for getStatus/setStatus",
          "phone": "digits, no +"
        }
      },
      "errors": ["NO_NUMBERS", "BAD_KEY"]
    },
    "getStatus": {
      "action": "getStatus",
      "description": "Poll SMS / activation state by local activation_id.",
      "params": {
        "action": { "required": true, "value": "getStatus" },
        "id": { "required": true, "note": "local 24-hex activation_id" },
        "api_key": { "required": "unless x-api-key header" }
      },
      "success_or_state": {
        "http_status": 200,
        "content_type": "text/plain",
        "bodies": [
          "STATUS_WAIT_CODE",
          "STATUS_OK:<sms_code>",
          "ACCESS_CANCEL",
          "NO_ACTIVATION",
          "BAD_ACTION",
          "BAD_KEY"
        ]
      },
      "unknown_id": "NO_ACTIVATION"
    },
    "setStatus": {
      "action": "setStatus",
      "description": "Change activation state.",
      "params": {
        "action": { "required": true, "value": "setStatus" },
        "id": { "required": true, "note": "local 24-hex activation_id" },
        "status": { "required": true },
        "api_key": { "required": "unless x-api-key header" }
      },
      "status_values": {
        "1": { "meaning": "ready / confirm", "typical_body": "ACCESS_READY" },
        "6": { "meaning": "activation sent", "typical_body": "ACCESS_ACTIVATION" },
        "-1": { "meaning": "cancel", "typical_body": "ACCESS_CANCEL" },
        "8": { "meaning": "cancel", "typical_body": "ACCESS_CANCEL" },
        "10": { "meaning": "other fail", "typical_body": "partner text, often ACCESS_CANCEL" }
      },
      "errors": ["NO_ACTIVATION", "BAD_STATUS", "BAD_KEY"],
      "missing_status": "BAD_STATUS"
    }
  },
  "unknown_action": "BAD_ACTION",
  "stats": {
    "scope": "getNumberSelected issues only; first write wins",
    "columns": {
      "issued": "number issued",
      "success": "getStatus STATUS_OK",
      "fail": "getStatus NO_ACTIVATION/ACCESS_CANCEL/STATUS_CANCEL or setStatus -1/8",
      "fail_other": "setStatus 10, or no terminal status for 10 minutes"
    }
  },
  "suggested_flow": [
    "GET /stubs/handler_api.php?api_key=KEY&action=getNumber&service=ig&country=ger → NO_NUMBERS or BAD_KEY",
    "GET /api/getNumberSelected?api_key=KEY&service=ig → JSON activation_id, phone (only if human asked to buy)",
    "GET handler?action=getStatus&id=activation_id → STATUS_WAIT_CODE | STATUS_OK:code | ACCESS_CANCEL",
    "GET handler?action=setStatus&id=activation_id&status=-1"
  ],
  "out_of_scope": {
    "auth": "login cookie",
    "api_key_rejected_as": { "http_status": 401, "body": { "detail": "login required" } },
    "paths": [
      "/api/state",
      "/api/selected",
      "/api/result",
      "/api/refresh",
      "/api/messages",
      "/api/messages/settings",
      "/api/codes",
      "/api/selection",
      "/api/admin/*",
      "/api/health",
      "/",
      "/admin"
    ],
    "note": "GET /api/selected is the human-monitor twin of getNumberSelected. Agents must call getNumberSelected."
  }
}
