Skip to content

Viewer API#

Your own scripts and tools can manage the sessions of a machine that runs Viewer: add sessions, change their proxies, start and stop them, read their status, and get told about changes as they happen. The API runs inside the Viewer app on that machine. It is off by default and needs a key.

Turn it on#

  1. Open Settings and set API to On. Leave the port at 37288 unless another program uses it.
  2. Click Save. Viewer creates a key the first time.
  3. Open Settings again to copy the Key and the address into your tool.

Add --enable-api when installing. The script prints the address and the key at the end. Pass --api-key=<key> on the next run to keep the same key, and --api-port to change the port.

The API answers on every address of the machine, so a tool on another machine uses the machine's IP with the same port. Anyone holding the key can control the sessions of that machine: keep it private, and make a new key (New key, then Save) if it leaks.

Quick start#

Every endpoint lives under http://127.0.0.1:37288 and needs the key in an Authorization header: Authorization: Bearer <key>. Reads are GET with parameters in the query; writes are POST with a JSON body.

Viewer must be open with the API turned on: Settings, API: On, Save (on Linux and Docker, --enable-api). The key and the address are in the same place.

The examples use curl in a POSIX shell (macOS, Linux, Git Bash). In Windows PowerShell call curl.exe, and put the JSON body in a file passed with -d @body.json.

curl -H "Authorization: Bearer YOUR_API_KEY" "http://127.0.0.1:37288/v1/status"

Key and safety#

The key is 32 hex characters, one key for the endpoints below and the events WebSocket.

Send it in the Authorization header of every request: Authorization: Bearer <key>. A key in the URL is refused, except on the WebSocket, where browsers cannot set headers: there ?key= works too. A missing or wrong key answers 401 badKey.

The API listens on every address of the machine, so a tool on another machine reaches it with the machine's IP and the same port. The key is the only lock: anyone holding it can add, start, stop and delete the sessions of that machine. Keep it private; if it leaks, make a new key in Settings (New key, then Save), and give it to your tools.

Requests sent from a web page are always refused, so a site you visit cannot call the API.

Responses and errors#

Success is { "ok": true, "data": ... } with HTTP 200. A refusal of the whole request is { "ok": false, "code", "message", "details"? } with the status below. code never changes meaning; message is for people.

Endpoints that work on many sessions answer 200 and list the ids they could not act on in failed, each with id, code and message (next table).

Code HTTP Meaning
badKey 401 The key is missing or wrong. Send it as Authorization: Bearer <key>.
invalidParams 400 A field is wrong; details lists each one with its path.
invalidJson 400 The body is not valid JSON.
unsupportedMediaType 415 A POST body was sent without Content-Type: application/json.
notFound 404 No such endpoint, or no session with this id.
methodNotAllowed 405 Wrong HTTP method for this endpoint.
duplicate 409 Another session already uses this proxy, or the machine's own IP; details.id names it. Each IP can carry only one session.
busy 429 Too many requests at once. Retry shortly.
internal 500 Viewer hit an error.

Per-session results#

Codes found in failed[] of start, stop, delete and export.

Code Meaning
notFound No session with this id.

System#

GET /v1/status#

What this Viewer is doing: version, sessions, totals, machine load. paired is whether an access key is set. hits and points are the totals of this run, as the toolbar shows them. loadavg is [0, 0, 0] on Windows.

No parameters.

Request
curl -H "Authorization: Bearer YOUR_API_KEY" "http://127.0.0.1:37288/v1/status"
Response
{
  "ok": true,
  "data": {
    "app": {
      "name": "9Hits Viewer",
      "version": "6.0.1",
      "build": "2026-10-18"
    },
    "api": {
      "version": 1
    },
    "machine": {
      "note": "vps-de-01",
      "paired": true
    },
    "sessions": {
      "total": 12,
      "running": 10
    },
    "hits": 1480,
    "points": 44400,
    "uptimeSec": 86400,
    "cpu": {
      "loadavg": [
        0.42,
        0.38,
        0.35
      ]
    },
    "ram": {
      "total": 8589934592,
      "available": 3221225472
    }
  }
}

Sessions#

GET /v1/sessions#

List sessions with their live status. Returns every session of this machine, newest first, like the table. hits and points are numbers; client is null until the session has an IP.

Field Type Required Description
search string no The same search as the box above the table: id, note, proxy, client IP, status, quality.
running boolean no Only running (true) or only stopped (false) sessions.
type string no Only sessions with this connection type. Values: system, http, socks4, socks5, ssh, exproxy.
limit integer no Min 1, max 1000. Default 500.
offset integer no Min 0. Default 0.
Request
curl -H "Authorization: Bearer YOUR_API_KEY" "http://127.0.0.1:37288/v1/sessions?running=true&limit=50"
Response
{
  "ok": true,
  "data": {
    "items": [
      {
        "id": "7f3a9c2e1b4d",
        "note": "vps-de-01",
        "proxy": {
          "type": "http",
          "server": "203.0.113.10:8080",
          "user": "u1",
          "password": "p1",
          "exServer": ""
        },
        "client": {
          "ip": "203.0.113.10",
          "country": "DE"
        },
        "quality": {
          "isProxy": true,
          "speed": "good",
          "performance": "medium",
          "earnRate": 0.8
        },
        "hits": 42,
        "points": 1260,
        "status": "Viewing",
        "statusColor": "green",
        "running": true
      }
    ],
    "total": 1
  }
}

GET /v1/sessions/{id}#

One session.

No parameters.

Request
curl -H "Authorization: Bearer YOUR_API_KEY" "http://127.0.0.1:37288/v1/sessions/7f3a9c2e1b4d"
Response
{
  "ok": true,
  "data": {
    "id": "7f3a9c2e1b4d",
    "note": "vps-de-01",
    "proxy": {
      "type": "http",
      "server": "203.0.113.10:8080",
      "user": "u1",
      "password": "p1",
      "exServer": ""
    },
    "client": {
      "ip": "203.0.113.10",
      "country": "DE"
    },
    "quality": {
      "isProxy": true,
      "speed": "good",
      "performance": "medium",
      "earnRate": 0.8
    },
    "hits": 42,
    "points": 1260,
    "status": "Viewing",
    "statusColor": "green",
    "running": true
  }
}

POST /v1/sessions/create#

Add a session. Without proxy the session uses the machine's own IP (type system). A proxy another session already uses, or a second system session, answers duplicate. Pass start true to open it at once.

Field Type Required Description
note string no Free text shown in the note column.
proxy object no How the session reaches the internet.
proxy.type string no system is the machine's own IP (one session per machine); exproxy takes a proxy from a proxy pool (exServer). Required when creating. Values: system, http, socks4, socks5, ssh, exproxy.
proxy.server string no host:port for http, socks4, socks5 and ssh. user:pass@host:port and host:port:user:pass are split into user and password. SSH may omit the port (22).
proxy.user string no Proxy user name.
proxy.password string no Proxy password.
proxy.exServer string no exproxy only: the pool key or URL. Empty uses the 9Hits proxy pool.
start boolean no Start the session right after creating it. Default false.
Request
curl -X POST "http://127.0.0.1:37288/v1/sessions/create" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "note": "vps-de-01",
  "proxy": {
    "type": "http",
    "server": "u1:[email protected]:8080"
  },
  "start": true
}'
Response
{
  "ok": true,
  "data": {
    "id": "7f3a9c2e1b4d",
    "note": "vps-de-01",
    "proxy": {
      "type": "http",
      "server": "203.0.113.10:8080",
      "user": "u1",
      "password": "p1",
      "exServer": ""
    },
    "client": null,
    "quality": null,
    "hits": 0,
    "points": 0,
    "status": "Starting...",
    "statusColor": null,
    "running": true
  }
}

POST /v1/sessions/{id}/update#

Change the note or proxy of a session. Only the fields sent change. proxy is merged over the current one: sending just server keeps the type, user and password. A running session uses the new proxy from its next view.

Field Type Required Description
note string no Free text shown in the note column.
proxy object no How the session reaches the internet.
proxy.type string no system is the machine's own IP (one session per machine); exproxy takes a proxy from a proxy pool (exServer). Required when creating. Values: system, http, socks4, socks5, ssh, exproxy.
proxy.server string no host:port for http, socks4, socks5 and ssh. user:pass@host:port and host:port:user:pass are split into user and password. SSH may omit the port (22).
proxy.user string no Proxy user name.
proxy.password string no Proxy password.
proxy.exServer string no exproxy only: the pool key or URL. Empty uses the 9Hits proxy pool.
Request
curl -X POST "http://127.0.0.1:37288/v1/sessions/7f3a9c2e1b4d/update" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "proxy": {
    "server": "203.0.113.11:8080"
  }
}'
Response
{
  "ok": true,
  "data": {
    "id": "7f3a9c2e1b4d",
    "note": "vps-de-01",
    "proxy": {
      "type": "http",
      "server": "203.0.113.11:8080",
      "user": "u1",
      "password": "p1",
      "exServer": ""
    },
    "client": {
      "ip": "203.0.113.10",
      "country": "DE"
    },
    "quality": {
      "isProxy": true,
      "speed": "good",
      "performance": "medium",
      "earnRate": 0.8
    },
    "hits": 42,
    "points": 1260,
    "status": "Viewing",
    "statusColor": "green",
    "running": true
  }
}

POST /v1/sessions/import#

Add sessions in bulk, like the Import button. text is one proxy per line (host:port, host:port;user;pass, user:pass@host:port, optionally starting with the type: socks5;host:port;user;pass), or the JSON that export returns. JSON entries with an existing id update that session; proxy lines already used by a session or repeated in the text are skipped.

Field Type Required Description
text string yes Proxy lines or exported JSON.
type string no Type for lines that do not name one. Values: system, http, socks4, socks5, ssh, exproxy. Default "http".
note string no Note for every session created from a line without a type.
Request
curl -X POST "http://127.0.0.1:37288/v1/sessions/import" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "text": "203.0.113.10:8080;u1;p1\nsocks5;203.0.113.11:1080",
  "type": "http",
  "note": "batch-1"
}'
Response
{
  "ok": true,
  "data": {
    "added": [
      "7f3a9c2e1b4d",
      "0c1d2e3f4a5b"
    ],
    "updated": [],
    "skipped": 0
  }
}

GET /v1/sessions/export#

Sessions as a proxy list or as JSON, like the Export button. proxies gives one line per session with a proxy (system sessions are left out): type;server[;user][;password]. json gives id, note and proxy of every session, which import takes back.

Field Type Required Description
format string no Values: proxies, json. Default "proxies".
ids string[] no Only these sessions; all of them when absent.
Request
curl -H "Authorization: Bearer YOUR_API_KEY" "http://127.0.0.1:37288/v1/sessions/export?format=proxies"
Response
{
  "ok": true,
  "data": {
    "format": "proxies",
    "count": 2,
    "text": "http;203.0.113.10:8080;u1;p1\nsocks5;203.0.113.11:1080",
    "failed": []
  }
}

POST /v1/sessions/start#

Start sessions. Answers at once; the session reports its progress in status (see GET /v1/sessions and the session.status event). Sessions already running are listed in unchanged.

Field Type Required Description
ids string[] no Session ids.
all boolean no Every session, instead of ids.
Request
curl -X POST "http://127.0.0.1:37288/v1/sessions/start" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "ids": [
    "7f3a9c2e1b4d",
    "0c1d2e3f4a5b"
  ]
}'
Response
{
  "ok": true,
  "data": {
    "started": [
      "7f3a9c2e1b4d"
    ],
    "unchanged": [
      "0c1d2e3f4a5b"
    ],
    "failed": []
  }
}

POST /v1/sessions/stop#

Stop sessions. Closes the browser of each running session. Sessions not running are listed in unchanged.

Field Type Required Description
ids string[] no Session ids.
all boolean no Every session, instead of ids.
Request
curl -X POST "http://127.0.0.1:37288/v1/sessions/stop" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "all": true
}'
Response
{
  "ok": true,
  "data": {
    "stopped": [
      "7f3a9c2e1b4d"
    ],
    "unchanged": [],
    "failed": []
  }
}

POST /v1/sessions/delete#

Delete sessions, running ones included. Stops the session if it runs, then removes it with its browser data. There is no confirmation and no undo.

Field Type Required Description
ids string[] yes Session ids.
Request
curl -X POST "http://127.0.0.1:37288/v1/sessions/delete" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "ids": [
    "7f3a9c2e1b4d"
  ]
}'
Response
{
  "ok": true,
  "data": {
    "deleted": [
      "7f3a9c2e1b4d"
    ],
    "failed": []
  }
}

POST /v1/sessions/clear#

Delete every session, like Clear all on the Remote page. Stops what runs, then removes every session with its browser data. There is no confirmation and no undo.

No parameters.

Request
curl -X POST "http://127.0.0.1:37288/v1/sessions/clear" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
Response
{
  "ok": true,
  "data": {
    "deleted": 12
  }
}

Settings#

GET /v1/settings#

Machine settings. The same fields as Machine settings on the Remote page. cacheLimit is bytes: -1 is the default, 0 turns the cache off.

No parameters.

Request
curl -H "Authorization: Bearer YOUR_API_KEY" "http://127.0.0.1:37288/v1/settings"
Response
{
  "ok": true,
  "data": {
    "note": "vps-de-01",
    "hideBrowser": true,
    "allowPopups": true,
    "allowAdult": false,
    "allowCoinMn": true,
    "cacheLimit": 104857600,
    "autoStart": true
  }
}

POST /v1/settings/update#

Change machine settings. Only the fields sent change. hideBrowser, allowPopups, allowAdult, allowCoinMn and cacheLimit apply to sessions started afterwards; autoStart applies at the next launch of the app.

Field Type Required Description
note string no Machine name, shown on the Remote page.
hideBrowser boolean no Hide the browser windows.
allowPopups boolean no Accept campaigns with popups.
allowAdult boolean no Accept campaigns with adult content.
allowCoinMn boolean no Accept campaigns with crypto mining.
cacheLimit integer no Disk cache limit per session in bytes; -1 default, 0 off. Min -1.
autoStart boolean no Start every session when the app opens.
Request
curl -X POST "http://127.0.0.1:37288/v1/settings/update" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "note": "vps-de-01",
  "hideBrowser": true
}'
Response
{
  "ok": true,
  "data": {
    "note": "vps-de-01",
    "hideBrowser": true,
    "allowPopups": true,
    "allowAdult": false,
    "allowCoinMn": true,
    "cacheLimit": 104857600,
    "autoStart": true
  }
}

App#

POST /v1/app/exit#

Stop every session and quit the app. Sessions close first; the app exits about ten seconds later, and the API goes away with it (the app.exiting event is sent). Under the Linux install script or Docker the supervisor opens the app again.

Field Type Required Description
reason string no Shown in the app's log.
Request
curl -X POST "http://127.0.0.1:37288/v1/app/exit" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "reason": "nightly restart"
}'
Response
{
  "ok": true,
  "data": {
    "exiting": true
  }
}

Events (WebSocket)#

Open a WebSocket to /v1/events?key=<key> (an Authorization header works too) to be told about changes instead of polling. Each message is one JSON text frame { "type", "ts", "data" }.

Send the text ping to get a pong. Send text frames only: a binary frame closes the connection.

Type Data
session.status the live part of a session, as GET /v1/sessions/{id} returns it without note and proxy: sent when its status, running, client, hits, points or quality change
session.added { ids }: new sessions, from the API, the app or the Remote page
session.updated { ids }: sessions whose note or proxy changed
session.deleted { ids }: removed sessions
settings.updated the machine settings, as GET /v1/settings returns them
app.exiting {}: Viewer is closing or restarting; the API goes away until it is back
const ws = new WebSocket("ws://127.0.0.1:37288/v1/events?key=YOUR_API_KEY");
ws.onmessage = (e) => console.log(JSON.parse(e.data));

Limits#

What Limit
HTTP HTTP/1.1; a body needs a Content-Length header (chunked bodies are not supported)
Request body 4 MB
Requests running at once 64 (more answer busy)
Session ids per call 1000
Sessions per list page 1000 (limit)
Import text 1 MB
WebSocket connections 32

Troubleshooting#

Connection refused: the API is off, Viewer is closed, or the port changed. The address and port are in Settings; on Linux and Docker the install script prints them.

401 badKey: send Authorization: Bearer <key>. Only the WebSocket takes ?key= in the URL.

Use 127.0.0.1, not localhost, in tools on the same machine: the API listens on IPv4 only, and some tools try IPv6 first for localhost.

Empty reply or connection reset: send HTTP/1.1 and give the body a Content-Length header. HTTP/1.0, chunked bodies and bodies over 4 MB are closed without an answer. Libraries set Content-Length for a string or bytes body; a stream or generator body is sent chunked.

403 with the plain text "origin rejected": the request carried an Origin header, as calls from a web page or browser extension do. Call the API from a script or tool.

The API could not start (shown in Settings): the port is in use. Pick another port and update your tools.

409 duplicate: another session already uses that proxy, or the machine's own IP; details.id names it. Change that session instead, or use another proxy.

Making a new key stops every tool that uses the old one until it gets the new one.