Skip to content

REST API

The customer REST API is served from the regional URL assigned to the rental. Examples use https://uswest1.aimlib.com.

Authentication

Send the customer API key as a bearer token on every /v1 request:

Authorization: Bearer <api-key>

API keys can carry these scopes:

Scope Allows
proxies.read List leased devices, proxy endpoints, browser state, and footprints
sessions.write Start and stop browsers, set footprints, rotate IPs, switch carriers, and poll those operations

Ticket routes require an authenticated active customer but no additional scope. A missing, expired, revoked, or malformed key returns 401 with {"error":"unauthorized"}. A missing scope returns 403 with {"error":"forbidden"}.

Common behavior

  • Request and response bodies use JSON unless noted.
  • Customer keys are limited to 6,000 requests per hour. A limit response is 429 and can include Retry-After.
  • UUID path values are strings.
  • Timestamps are ISO 8601 UTC values.
  • Unknown or cross-account resource identifiers return the resource's normal not-found response.
  • Do not log bearer tokens, proxy URLs, or browser connection tokens.

GET /v1/devices

Lists devices in the caller's active rentals. Requires proxies.read.

curl --fail-with-body \
  -H "Authorization: Bearer $AIMLIB_API_KEY" \
  "$AIMLIB_BASE_URL/v1/devices"

Response: 200 OK.

[
  {
    "device_id": "550e8400-e29b-41d4-a716-446655440000",
    "region": "uswest1",
    "carrier": "tmobile",
    "current_egress_ip": "203.0.113.45",
    "lease": {
      "id": "660e8400-e29b-41d4-a716-446655440000",
      "ends_at": "2026-12-31T23:59:59Z"
    },
    "proxy": {
      "id": "770e8400-e29b-41d4-a716-446655440000",
      "protocols": ["http", "socks5"],
      "http_url": "http://user:password@uswest1.aimlib.com:43117",
      "socks5_url": "socks5://user:password@uswest1.aimlib.com:43117",
      "socks5h_url": "socks5h://user:password@uswest1.aimlib.com:43117",
      "status": "active"
    },
    "browser": {"available": true}
  }
]

proxy.url and proxy.protocol can also appear for compatibility. New clients should use http_url, socks5_url, or socks5h_url; all three identify the same listener and credentials. proxy is absent while endpoint setup is incomplete. current_egress_ip is the latest public address observed for the rental and can change.

POST /v1/devices/{id}/browser

Creates the remote-browser session for a leased device. Requires sessions.write. An empty JSON body is valid.

{
  "ttl": 600,
  "idle_timeout": 120,
  "footprint": "pixel-9"
}
Field Type Behavior
ttl integer seconds Defaults to and is capped at 1,800 seconds; also capped by rental end
idle_timeout integer seconds Defaults to 120 seconds
footprint string Optional slug returned by the footprints endpoint

Response: 201 Created.

{
  "session_id": "880e8400-e29b-41d4-a716-446655440000",
  "status": "provisioning",
  "connect_url": "<authenticated-connection-url>",
  "connect_token": "<session-secret>",
  "region": "uswest1",
  "ready_in_s": 30,
  "egress_ip": null,
  "expires_at": "2026-07-14T20:10:00Z",
  "desired_footprint": "pixel-9",
  "max_tabs": 5
}

Important errors:

Status Code Meaning
400 footprint_not_clean Footprint is not available for this device
403 lease_inactive The active-rental requirement is not met
409 browser_unavailable Remote browser is not currently available
409 data_cap_exceeded The account's usage allowance is exhausted
429 capacity_unavailable Device or account session capacity is in use
503 browser_unavailable Browser is temporarily unreachable; retry shortly

GET /v1/devices/{id}/browser

Returns the active remote-browser session for the caller's rented device. Requires proxies.read. Poll until status is ready before connecting.

Response: 200 OK.

{
  "session_id": "880e8400-e29b-41d4-a716-446655440000",
  "status": "ready",
  "connect_url": "<authenticated-connection-url>",
  "egress_ip": "203.0.113.45",
  "expires_at": "2026-07-14T20:10:00Z",
  "started_at": "2026-07-14T20:00:22Z",
  "desired_footprint": "pixel-9",
  "applied_footprint": "pixel-9"
}

No active session returns 404 with {"error":"session_not_found"}. The connection token is returned only by the create call.

DELETE /v1/devices/{id}/browser

Requests a stop for the active remote-browser session. Requires sessions.write.

Response: 200 OK.

{"status":"stopping"}

No active session returns 404 with {"error":"session_not_found"}. Poll the browser endpoint until it returns 404 when stop completion matters.

GET /v1/devices/{id}/footprints

Lists footprint choices available for the rented device. Requires proxies.read.

Response: 200 OK.

{
  "device_id": "550e8400-e29b-41d4-a716-446655440000",
  "footprints": [
    {"slug":"pixel-9","name":"Google Pixel 9"}
  ]
}

No option is guaranteed across all devices. An inactive rental returns 403 with {"error":"lease_inactive"}.

POST /v1/devices/{id}/footprint

Changes the desired footprint for the active browser session. Requires sessions.write.

{"footprint":"pixel-9"}

Use an empty string to restore the device default. Response: 200 OK.

{
  "session_id": "880e8400-e29b-41d4-a716-446655440000",
  "desired_footprint": "pixel-9",
  "applied_footprint": ""
}

Poll the browser endpoint until desired and applied values match. Without an active browser, the response is 409 with {"error":"no_session"}. An unavailable option returns 400 with {"error":"footprint_not_clean"}.

POST /v1/devices/{id}/rotate-ip

Requests a new public mobile IP. Requires sessions.write.

{"wait":true,"timeout_s":210}

wait defaults to true. The accepted timeout range is normalized by the service. A completed response is 200 OK:

{
  "operation_id": "990e8400-e29b-41d4-a716-446655440000",
  "type": "ip_rotation",
  "status": "succeeded",
  "new_ip": "203.0.113.86"
}

With wait:false, the response is 202 Accepted with operation_id and status:"queued". Poll the operation endpoint. Network operations on one device must be at least 30 seconds apart; otherwise the response is 429 with {"error":"rate_limited"}.

POST /v1/devices/{id}/carrier

Switches to a carrier available for the rental. Requires sessions.write.

{"carrier":"tmobile","wait":true,"timeout_s":165}

Accepted identifiers are tmobile, att, and verizon. Device-specific availability still applies. A successful 200 OK response can include carrier and new_ip. With wait:false, the response is 202 Accepted and can be polled. An invalid identifier returns 400 with {"error":"bad_carrier"}.

GET /v1/operations/{id}

Returns an IP-rotation or carrier-switch operation for a device in the caller's current active rental. Requires sessions.write.

{
  "operation_id": "990e8400-e29b-41d4-a716-446655440000",
  "type": "carrier_switch",
  "status": "running"
}

Statuses are queued, sent, running, succeeded, failed, or timeout. Successful responses can include the allowlisted new_ip and carrier fields. Failed and timed-out responses include only a stable public error and message. Invalid, unsupported, expired-rental, and cross-account IDs all return 404 with {"error":"operation_not_found"}.

POST /v1/tickets

Creates a support ticket. No extra scope is required.

{"subject":"Browser does not become ready","body":"Device ID ...; observed around 20:00 UTC."}

Both fields are required. Response: 201 Created with the ticket and its first message.

GET /v1/tickets

Lists the caller's tickets without full message threads. No extra scope is required. Response: 200 OK with an array.

GET /v1/tickets/{id}

Returns one caller-owned ticket including messages. No extra scope is required. Missing and cross-account IDs return 404 with {"error":"ticket_not_found"}.

POST /v1/tickets/{id}/messages

Adds a customer reply and reopens a closed ticket. No extra scope is required.

{"body":"The issue also occurs on a second permitted destination."}

Response: 201 Created with the updated ticket and message thread.

POST /v1/tickets/{id}/close

Closes the ticket from the customer's side. No extra scope is required. Response: 200 OK. A ticket is fully closed only after both the customer and aimlib support close it.

Error envelope

Request failures use a stable error code and can include a safe message:

{"error":"browser_unavailable","message":"the browser is temporarily unavailable; retry shortly"}

Do not branch on human-readable messages. Use HTTP status plus error, and see Limits and errors for retry guidance.