Tokens API
Every proxy token: list and read, and usage. Unlimited tokens that can move also have location, rotation, and a new IP on request.
List and read#
GET /api/v1/tokens
Authorization: Bearer lit_...
| Parameter | Type | Rules |
|---|---|---|
limit |
integer | Integer 1-100; default 50. |
cursor |
string | The previous page's page |
type |
string | unlimited or ppg. |
status |
string | active, expired, or disabled. |
packageId |
integer | An owned package's public numeric ID. An unknown or foreign value returns an empty list. A malformed value is INVALID |
Results are ordered newest first. Keep requesting the returned page.nextCursor until it is null.
GET /api/v1/tokens/{tokenId}
Authorization: Bearer lit_...
Returns one token in the identical shape as a GET /tokens list element, including its username and password. An unknown, unowned, or unparseable tokenId is 404 TOKEN_NOT_FOUND; the response never discloses another user's token.
Examples#
An unlimited datacenter token, on a package with allowed IPs and a movable location:
{
"id": 12345,
"type": "unlimited",
"status": "active",
"label": "Production crawler",
"createdAt": "2026-08-01T10:15:00.000Z",
"expiresAt": "2026-12-01T00:00:00.000Z",
"ingress": {
"hub": "us-11",
"protocol": "http",
"mode": "assigned",
"host": "hub-us-11-1.litport.net",
"port": 31337,
"status": "available"
},
"packageId": 45,
"ipAuth": { "host": "hub-us-11-1.litport.net", "port": 31338 },
"location": { "country": "us", "region": "new-york", "city": "new-york-city" },
"rotation": { "intervalSec": 600, "options": [600, 1800, 3600], "changeable": true },
"username": "proxy-username",
"password": "proxy-password"
}
A pay-per-GB token, locked to one pool:
{
"id": 12399,
"type": "ppg",
"status": "active",
"label": "Residential crawler",
"createdAt": "2026-08-01T10:15:00.000Z",
"expiresAt": null,
"ingress": {
"hub": "example-hub",
"protocol": "http",
"mode": "request-selected",
"supportedProtocols": ["http", "https", "socks5"]
},
"poolSelection": {
"mode": "fixed",
"pool": { "authKey": "residential-main", "name": "Residential Main" }
},
"packageId": null,
"ipAuth": null,
"username": "proxy-username",
"password": "proxy-password"
}
Example identifiers and hubs in this documentation are illustrative.
Field semantics#
type is ppg or unlimited. status is the token's own lifecycle: disabled when explicitly disabled, otherwise expired when its expiration has passed, otherwise active. It is never hub-unavailable; that value belongs to ingress.status, below.
For a PPG token, ingress.mode is request-selected: there is no single host or port, because the hub, protocol and pool are chosen per request or export. ingress.hub and ingress.protocol are dashboard/export preferences, and ingress.supportedProtocols lists the choices. Take the host from a pool's hubs and the port from its httpPort/socks5Port in Pay-per-GB.
For an unlimited token, ingress.mode is assigned: the token resolves to exactly one endpoint, always reported as ingress.host and ingress.port. ingress.status is available or hub-unavailable. hub-unavailable means the token names a hub that is no longer in service: ingress.host and ingress.port are then null, and the credentials cannot be used until support moves the token to a current hub.
packageId is the public numeric package ID when the token belongs to an owned package, otherwise null; read the package with Packages. ipAuth is {host, port}, the token's passwordless endpoint, or null when the token has none right now. It accepts connections only from the package's allowed IPs.
location is present only on a token whose location can move; Location lists which tokens qualify. Its shape depends on kind: a datacenter or ISP token reports {country, region, city}; a mobile token reports {country, city, carrier}.
rotation is {intervalSec, options, changeable}, present on every token that rotates on a timer. See Rotation.
rotateUrl is the Private Device "change IP now" link, present only when the token can use it. Treat it as secret as the password next to it.
username and password are the proxy credentials for the token. Use them with the endpoint shown in ingress or the dashboard.
Usage#
Read customer-visible traffic for one owned token.
GET /api/v1/tokens/{tokenId}/usage
Authorization: Bearer lit_...
Query parameters#
| Parameter | Rules |
|---|---|
from |
Inclusive canonical UTC ISO-8601 timestamp. |
to |
Exclusive canonical UTC ISO-8601 timestamp; must be after from. |
groupBy |
hour, day, or month; default day. |
include |
Comma-separated domains and/or charges. charges requires groupBy=hour. |
When the range is omitted, the API uses the 30 complete UTC days before the current UTC day. Hour ranges are limited to 31 days, day ranges to 366 days, and month ranges to 36 months.
Example#
curl --get 'https://litport.net/api/v1/tokens/12345/usage' \
-H "Authorization: Bearer $LITPORT_API_KEY" \
--data-urlencode 'from=2026-08-01T00:00:00Z' \
--data-urlencode 'to=2026-08-03T00:00:00Z' \
--data-urlencode 'groupBy=day'
{
"data": {
"tokenId": 12345,
"range": {
"from": "2026-08-01T00:00:00.000Z",
"to": "2026-08-03T00:00:00.000Z",
"groupBy": "day",
"timezone": "UTC"
},
"trafficBasis": "customer_chargeable",
"summary": {
"requests": 42,
"uploadedBytes": "12000",
"downloadedBytes": "88000",
"totalBytes": "100000",
"cache": {
"hits": 12,
"misses": 4,
"bypasses": 2,
"hitRate": 0.75,
"freeUploadedBytes": "0",
"freeDownloadedBytes": "24000",
"freeTotalBytes": "24000",
"savedCostUnits": "0.0324"
}
},
"buckets": [
{
"start": "2026-08-01T00:00:00.000Z",
"end": "2026-08-02T00:00:00.000Z",
"requests": 42,
"uploadedBytes": "12000",
"downloadedBytes": "88000",
"totalBytes": "100000",
"cache": {
"hits": 12,
"misses": 4,
"bypasses": 2,
"hitRate": 0.75,
"freeUploadedBytes": "0",
"freeDownloadedBytes": "24000",
"freeTotalBytes": "24000",
"savedCostUnits": "0.0324"
}
}
]
}
}
Usage semantics#
The interval is half-open: [from, to). Buckets are ordered by start time and are sparse; a missing bucket means no stored aggregate row, not an API error.
For PPG tokens, trafficBasis is customer_chargeable, so the traffic totals match the traffic shown to the customer. For fixed-price/unlimited tokens, it is observed; those existing traffic totals remain the observed transfer basis. Large byte totals are decimal strings so clients can preserve exact values. requests is a JSON integer.
Cache benefit#
Every bucket and the summary contain cache. hits, misses, and bypasses are JSON integers; hitRate is hits / (hits + misses) and is 0 when there were no hits or misses. freeUploadedBytes, freeDownloadedBytes, and freeTotalBytes are non-negative base-10 decimal strings, with freeTotalBytes equal to the two directional values added together. Legacy periods without cache telemetry report zeroes for these fields.
For PPG tokens only, cache.savedCostUnits is a non-negative base-10 decimal string for the audited charge avoided at the price effective when the traffic occurred. The field is deliberately omitted, not set to 0 or null, for unlimited tokens because no per-GB customer charge was avoided.
The token must belong to the authenticated user. An unknown or unowned token returns the same 404 TOKEN_NOT_FOUND response.
Charge breakdown#
For a PPG token, request include=charges&groupBy=hour to add the persisted charge to each hourly bucket. The expansion is rejected for unlimited tokens. A versioned charge contains the pool's public key and name, raw and charged upload/download bytes, contributing hubs and protocols, snapshotted base/effective rates and discount, and the exact amount deducted. A row without snapshots is labeled Legacy PPG usage; the API does not guess its pool or rate from current token settings.
Charge components are the billing record. Upload and download are both charged. A reserved charge can grow while late traffic for the hour arrives; a finished charge is final.
Domain breakdown#
Request include=domains to add domainBreakdown to data:
{
"domainBreakdown": {
"limit": 150,
"totalDestinationCount": 151,
"displayedCount": 150,
"truncated": true,
"range": {
"from": "2026-08-01T00:00:00.000Z",
"to": "2026-08-03T00:00:00.000Z",
"timezone": "UTC",
"periodClamped": false,
"endClamped": false
},
"items": [{
"protocol": "tcp",
"destination": "example.com",
"port": 443,
"requests": 42,
"uploadedBytes": "12000",
"downloadedBytes": "88000",
"totalBytes": "100000",
"lastSeenAt": "2026-08-01T12:00:00.000Z",
"firstSeenAt": "2026-08-01T00:00:00.000Z"
}],
"other": {
"protocol": null,
"destination": "Other",
"port": null,
"requests": 1,
"uploadedBytes": "1",
"downloadedBytes": "2",
"totalBytes": "3",
"lastSeenAt": null,
"firstSeenAt": null
}
}
}
The response includes at most the 150 destinations with the highest total traffic. Every remaining destination and telemetry overflow is combined into other; truncated reports when that grouping occurred. TCP requests are successful request counts and UDP requests are inbound datagrams. Domain data is stored as whole-hour UTC buckets and is limited to the most recent 31 days of the requested interval. The returned UTC range is the effective bucket interval: a past partial start is rounded down to its hour and a past partial end is rounded up to its next hour. An end clamped to the current time remains at that exact time, so the response does not claim coverage past the current time. The range also reports periodClamped or endClamped when adjusted.
Location#
A datacenter or ISP token, and a mobile token whose city or carrier can change, report a location field and support this API. Which tokens qualify, and how a change behaves:
| Token | Changes | Wait |
|---|---|---|
| Datacenter |
Any time | None |
| Datacenter |
A new IP in the chosen location | Once a week |
| Multi-location mobile (paid add-on) | Any time | None |
| Standard US Shared Mobile | City and carrier together | Once per 48 hours |
| Everything else (PPG, Private Device, non-US mobile, a day plan, or a legacy token with no port type) | Not supported | None |
GET /api/v1/tokens/{tokenId}/location
Authorization: Bearer lit_...
{
"data": {
"tokenId": 12345,
"kind": "datacenter",
"mode": "rotating",
"target": { "country": "us", "region": "new-york", "city": "new-york-city" },
"effective": {
"country": "us", "region": "new-york", "city": "new-york-city",
"labels": { "region": "New York", "city": "New York City" }
},
"status": "ready",
"canChangeNow": true,
"nextChangeAt": null
}
}
kind is datacenter, isp, or mobile. mode is rotating or static. target is the saved choice; a datacenter/ISP region/city can be "any". effective is the actual location of the IP the token holds right now, or null when it is not yet known. status is pending for a short time while a change propagates, ready once it has, unavailable when the target location currently has no usable IP or device (the token keeps its last one rather than losing its route), package-inactive when the token's package is not active, or allowance-exhausted, only for an older Multi-location package that still has a traffic cap.
List available locations#
GET /api/v1/tokens/{tokenId}/location/options?level=region
GET /api/v1/tokens/{tokenId}/location/options?level=city®ion=new-york
Datacenter/ISP: level=region, then level=city®ion=…. Mobile: level=city, then level=carrier&city=…. Each option is {"value": …, "label": …}, and the list holds only options this token can use right now.
Change location#
Datacenter/ISP:
curl -X PUT 'https://litport.net/api/v1/tokens/12345/location' \
-H "Authorization: Bearer $LITPORT_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"region": "new-york", "city": "new-york-city"}'
Mobile:
curl -X PUT 'https://litport.net/api/v1/tokens/98765/location' \
-H "Authorization: Bearer $LITPORT_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"city": "seattle", "carrier": "verizon"}'
Send exactly the fields for that token's kind: datacenter/ISP is region and city; mobile is city and carrier. country is optional and must be "us" when sent. No precondition header is required or honored: the latest committed PUT becomes the token's desired target, and a PUT identical to the currently saved target succeeds without a new change.
A rotating datacenter/ISP token moves to a matching IP at once. A static one gets a new IP in the chosen location, using its once-a-week allowance; a repeat inside that week is 409 CHANGE_LIMIT with nextChangeAt. A Multi-location token moves at once, with no wait. A standard US Shared Mobile token can change once per 48 hours; a repeat inside that window is also 409 CHANGE_LIMIT.
For any mobile token, the city is always kept: if the chosen carrier has no working phone in that city, the proxy uses another carrier in the same city instead, and effective shows the carrier actually in use.
Location errors#
| Status | Code | Meaning |
|---|---|---|
400 |
INVALID |
The body has the wrong fields for this token's kind. |
400 |
INVALID |
Not a supported US location or carrier, or "any" for a standard Shared Mobile token. |
400 |
INVALID / INVALID |
Options: a level this token doesn't use, or a missing region/city. |
404 |
TOKEN |
Unknown, unowned, or malformed tokenId. |
409 |
NOT |
This token's location can't be changed. |
409 |
PACKAGE |
The token or its package is not active. |
409 |
CHANGE_LIMIT |
Inside the weekly or 48-hour wait. The error carries nextChangeAt and a matching Retry-After. |
409 |
LOCATION |
No usable IP or working phone there right now. Nothing changed. |
409 |
CONCURRENT |
Another change to this token landed at the same moment. Retry the same PUT. |
Rotation#
PUT /api/v1/tokens/{tokenId}/rotation
Authorization: Bearer lit_...
Content-Type: application/json
{"intervalSec": 600}
{
"data": { "tokenId": 12345, "intervalSec": 600, "options": [600, 1800, 3600], "changeable": true }
}
intervalSec must be one of options, the same values the token's own rotation field lists. changeable is true for a rotating datacenter/ISP token, a standard US Shared Mobile token, and a US Private Device. A Private Device's options start at 5 minutes. A Multi-location token always reports its current phone's interval with changeable: false, because the phone the proxy is on sets its own timer; its PUT also answers 409 NOT_SUPPORTED, as does any other non-rotating token.
Rotation errors#
| Status | Code | Meaning |
|---|---|---|
400 |
INVALID |
The body is not exactly {"interval. |
400 |
INVALID |
intervalSec is not one of the token's options. |
404 |
TOKEN |
Unknown, unowned, or malformed tokenId. |
409 |
NOT |
The token doesn't rotate, or its interval is set by the phone (Multi-location) |
409 |
PACKAGE |
The token or its package is not active. |
New IP#
POST /api/v1/tokens/{tokenId}/new-ip
Authorization: Bearer lit_...
Static datacenter and ISP tokens only. A rotating token already changes IP with every rotation and has no single current IP to replace. The request takes no body: the service reuses the token's own saved location on every attempt, including a retry. Returns the same shape as Location, with a new effective IP.
This is the one write that is not safe to repeat blindly. If a response is lost or times out, read GET /tokens/{tokenId}/location before retrying: a nextChangeAt later than the current time means the change went through. Sending the POST again inside that week does not give a second IP; it returns 409 CHANGE_LIMIT.
New IP errors#
| Status | Code | Meaning |
|---|---|---|
404 |
TOKEN |
Unknown, unowned, or malformed tokenId. |
409 |
NOT |
The token is not a static datacenter or ISP proxy. |
409 |
PACKAGE |
The token or its package is not active. |
409 |
CHANGE_LIMIT |
This week's replacement is already used. The error carries nextChangeAt and a matching Retry-After. |
409 |
LOCATION |
No other usable IP in the saved location right now. Nothing changed. |
409 |
CONCURRENT |
Another change to this token landed at the same moment. Read the location, then retry. |