# Numbers API — Cursor agent contract

Public. No login. No API key required to read this file.

- HTML: `https://onlygirls.click/docs`
- Markdown (this file): `https://onlygirls.click/docs.md`
- JSON (same contract, structured): `https://onlygirls.click/docs.json`
- Plain-text alias: `https://onlygirls.click/llms.txt`

This document is the contract. Follow it exactly. Do not invent extra endpoints, parameters, or keys.

```
AUDIENCE: another Cursor agent
ORIGIN: https://onlygirls.click
READ: this file, /docs.json, or /docs — all public
WRITE/SMS: requires a human-provided key nk_...
NEVER invent or guess an API key
NEVER buy a real number while testing (use country=ger)
NEVER call partner hosts; only this origin
```

## What this service is

HTTPS monitor + SMS number API.

Two auth worlds:

| Surface | Auth | Use |
|---|---|---|
| SMS number API (`handler_api.php` / `getNumberSelected`) | API key `nk_...` | Get numbers, poll SMS code, cancel |
| Browser monitor (`/`, `/api/state`, `/admin`) | login cookie | Humans only. API keys do **not** work here |

SMS methods always return HTTP **200**, even for errors. Invalid key → plaintext `BAD_KEY`, not JSON 401.

## Rules for agents

1. Fetch this file or `/docs.json` before calling SMS methods.
2. Never invent or guess an API key. The human must provide `nk_...`.
3. Do **not** buy real numbers while testing. Use `country=ger` on `getNumber` (returns `NO_NUMBERS`). Use unknown 24-hex ids for `getStatus` / `setStatus`.
4. If you accidentally receive `ACCESS_NUMBER`, cancel immediately with `setStatus` `status=-1`.
5. After `getNumber` / `getNumberSelected`, store the **local** 24-hex `activation_id`. Never send a partner/upstream id; this API rewrites ids.
6. `getNumberSelected` success is **JSON**. Every other SMS success/error is **text/plain**.
7. `country` is ISO 3166-1 alpha-2, lowercase (`it`, `gb`, `tj`). `ger` is a safe no-number probe.
8. `operator` is the operator **display name** from the monitor table (example: `TIM - Telecom Italia S.p.A.`). Empty/`any` means any operator.
9. Do not call partner hosts directly. Only this origin.
10. Do not use `Authorization: Bearer`. Only `x-api-key` or `api_key`.
11. FastAPI Swagger is disabled. `/docs` is this contract, not `/openapi.json`.

## Base URLs (GET and POST are equivalent)

SMS entrypoints (API key required):

- `https://onlygirls.click/stubs/handler_api.php`
- `https://onlygirls.click/handler_api.php`
- `https://onlygirls.click/api/handler`
- `https://onlygirls.click/api/getNumberSelected` — `getNumberSelected` only; no `action` needed

Query, `x-api-key` header, `application/x-www-form-urlencoded`, `multipart/form-data`, and JSON body are merged. Non-empty query wins over body.

## API key

Pass **one** of (first match wins):

```
x-api-key: nk_...
?api_key=nk_...
POST body api_key=nk_...
```

Keys are created in `/admin` by a logged-in admin. This docs page never returns keys.

Missing, empty, or unknown key → HTTP 200, `text/plain`, body `BAD_KEY`.

## Methods

Four SMS methods. Unknown `action` → `BAD_ACTION`.

### getNumber

Issue a phone number for a service + country + operator.

| param | required | notes |
|---|---|---|
| `action` | yes | `getNumber` |
| `service` | yes | service code, e.g. `ig` |
| `country` | yes | ISO2 lowercase |
| `operator` | no | forwarded to partner; empty becomes `any` |
| `api_key` | yes unless header | `nk_...` |

Response: HTTP 200, `text/plain`

```
ACCESS_NUMBER:<activation_id>:<phone>
NO_NUMBERS
BAD_KEY
```

`<activation_id>` is 24 lowercase hex. Use it as `id` in `getStatus` / `setStatus`.

Safe probe (does not buy a number):

```bash
curl 'https://onlygirls.click/stubs/handler_api.php?api_key=nk_...&action=getNumber&service=ig&operator=any&country=ger'
# expected: NO_NUMBERS
```

### getNumberSelected

Pick one random **checked** operator from the monitor table (same pool as session-only `GET /api/selected`), then buy a number for that country with that operator name. This is the preferred method when the human already selected operators in the UI.

| param | required | notes |
|---|---|---|
| `action` | yes on handler URLs | `getNumberSelected` |
| `service` | no | limit to one service code; omit = all codes that have checked rows |
| `api_key` | yes unless header | `nk_...` |

Shortcut (no `action`): `GET/POST https://onlygirls.click/api/getNumberSelected`

Unknown or unchecked `service` → `NO_NUMBERS`.

Success: HTTP 200, `application/json`

```json
{
  "issue_id": "6b3c0e2e-2c1a-4f0e-9d5a-1c2b3a4d5e6f",
  "issued_at": "2026-09-20T13:00:00+00:00",
  "service": "ig",
  "id": "86|TIM - Telecom Italia S.p.A.|0",
  "country_id": 86,
  "code": "IT",
  "country_name": "Italy",
  "operator": "TIM - Telecom Italia S.p.A.",
  "rate": 238,
  "activation_id": "6aafd036afdd2a1cdba26133",
  "phone": "393925063783"
}
```

| field | type | meaning |
|---|---|---|
| `activation_id` | 24-hex string | local id for `getStatus` / `setStatus` |
| `phone` | string | digits, no `+` |
| `issue_id` | UUID | monitor stats only; do not send it back |
| `issued_at` | ISO-8601 UTC | when the number was issued |
| `service` | string | service code actually used |
| `id` | string | monitor row id (`country_id\|operator\|index`) |
| `country_id` | number | partner country id |
| `code` | string | ISO2 uppercase in this JSON (`IT`) |
| `country_name` | string | English country name |
| `operator` | string | display name sent to partner as `operator=` |
| `rate` | number | partner rate at issue time |

Use `activation_id` as `id`. `issue_id` is monitor stats only.

Errors stay plaintext: `NO_NUMBERS` (nothing checked, unknown service, or partner has no number), `BAD_KEY`.

```bash
curl 'https://onlygirls.click/api/getNumberSelected?api_key=nk_...&service=ig'
```

POST JSON is also valid:

```bash
curl -X POST 'https://onlygirls.click/api/getNumberSelected' \
  -H 'content-type: application/json' \
  -H 'x-api-key: nk_...' \
  -d '{"service":"ig"}'
```

### getStatus

Poll SMS / activation state by **local** id.

| param | required | notes |
|---|---|---|
| `action` | yes | `getStatus` |
| `id` | yes | `activation_id` from getNumber / getNumberSelected |
| `api_key` | yes unless header | `nk_...` |

Response: HTTP 200, `text/plain` (partner text is forwarded after id rewrite)

```
STATUS_WAIT_CODE
STATUS_OK:<sms_code>
ACCESS_CANCEL
NO_ACTIVATION
BAD_ACTION
BAD_KEY
```

Unknown local id → `NO_ACTIVATION`.

`STATUS_OK:852508` means the SMS code is `852508`. The monitor SMS table also tries to replace that bare code with the full SMS text when it arrives.

```bash
curl 'https://onlygirls.click/stubs/handler_api.php?api_key=nk_...&action=getStatus&id=6aafd036afdd2a1cdba26133'
```

### setStatus

Change activation state.

| param | required | notes |
|---|---|---|
| `action` | yes | `setStatus` |
| `id` | yes | local `activation_id` |
| `status` | yes | see table |
| `api_key` | yes unless header | `nk_...` |

Missing `status` → `BAD_STATUS`. Unknown local id → `NO_ACTIVATION`.

| `status` | meaning | typical body |
|---|---|---|
| `1` | ready / confirm | `ACCESS_READY` |
| `6` | activation sent | `ACCESS_ACTIVATION` |
| `-1` | cancel | `ACCESS_CANCEL` |
| `8` | cancel | `ACCESS_CANCEL` |
| `10` | other fail | partner text, often `ACCESS_CANCEL` |

Other: `NO_ACTIVATION`, `BAD_STATUS`, `BAD_KEY`.

To cancel a test purchase:

```bash
curl 'https://onlygirls.click/stubs/handler_api.php?api_key=nk_...&action=setStatus&id=ACTIVATION_ID&status=-1'
```

## Stats (monitor table, not returned by SMS methods)

Written **once** per `getNumberSelected` issue. Later `getStatus` / `setStatus` on the same id do not overwrite.

| event | column |
|---|---|
| number issued | issued |
| `getStatus` `STATUS_OK` | success |
| `getStatus` `NO_ACTIVATION` / `ACCESS_CANCEL` / `STATUS_CANCEL` | fail |
| `setStatus` `-1` or `8` | fail |
| `setStatus` `10` | fail_other |
| no terminal status for 10 minutes | fail_other |

`getNumber` (not selected) does not write monitor issue stats unless it went through `getNumberSelected`.

## Suggested agent flow

```
1. Confirm key with a safe call (does not buy a number):
   GET /stubs/handler_api.php?api_key=KEY&action=getNumber&service=ig&country=ger
   → NO_NUMBERS (key ok) or BAD_KEY

2. Production number (only if the human asked to buy):
   GET /api/getNumberSelected?api_key=KEY&service=ig
   → JSON {activation_id, phone, ...} or plaintext NO_NUMBERS

3. Poll every few seconds:
   GET ...&action=getStatus&id=activation_id
   → STATUS_WAIT_CODE | STATUS_OK:code | ACCESS_CANCEL | NO_ACTIVATION

4. When done or on failure:
   GET ...&action=setStatus&id=activation_id&status=-1
```

## Out of scope (login cookie, not API key)

These return JSON `{"detail":"login required"}` with HTTP 401 if called with only an API key:

`GET /api/state`, `GET /api/selected`, `POST /api/result`, `POST /api/refresh`, `GET /api/messages`, `POST /api/messages/settings`, `POST /api/codes`, `POST /api/selection`, `GET /api/admin/*`, `GET /api/health`, UI `/` `/admin`.

Do not use them for SMS automation. `GET /api/selected` is the human-monitor twin of `getNumberSelected`; agents must call `getNumberSelected` instead.

## Public URLs (no auth)

| URL | body |
|---|---|
| `GET /docs` | this spec as HTML |
| `GET /docs.md` | this spec as Markdown |
| `GET /docs.json` | this spec as JSON |
| `GET /llms.txt` | same Markdown as `/docs.md` |
| `GET /login` | login form |
| `GET /static/*` | CSS/JS/assets |

## Origin

Canonical HTTPS origin: `https://onlygirls.click` (port 443). HTTP/80 redirects to HTTPS.
