{
  "openapi": "3.1.0",
  "info": {
    "title": "Phone Harness API",
    "version": "2026-09-14",
    "description": "Public account and disposable Android session API. Open beta. Create responses are asynchronous; session duration includes provisioning, while billing begins at readiness. This specification documents the current supported client contract, not internal management interfaces. All example IDs, links, timestamps, limits, prices and digests are illustrative, not live credentials, measurements, or account entitlements.",
    "contact": {
      "name": "Phone Harness support",
      "email": "support@phone-harness.com"
    }
  },
  "servers": [
    {
      "url": "https://api.phone-harness.com",
      "description": "Production beta"
    }
  ],
  "security": [
    {
      "ApiKey": []
    }
  ],
  "tags": [
    {
      "name": "Sessions"
    },
    {
      "name": "Phone"
    },
    {
      "name": "ADB"
    },
    {
      "name": "Account"
    },
    {
      "name": "History"
    },
    {
      "name": "Service"
    }
  ],
  "paths": {
    "/sessions": {
      "post": {
        "summary": "Create a session",
        "operationId": "create-session",
        "tags": [
          "Sessions"
        ],
        "description": "Allocate an Android phone asynchronously. HTTP 202 means admitted, not ready. Poll the returned id. The deadline includes provisioning. Use one stable Idempotency-Key per logical Start. Pass your account's profile_id (from GET /me) to use your own phone, which is saved when the session ends and comes back the same way next time; omit it for a temporary phone that keeps nothing. Raw capabilities and runtime resource overrides are not supported. Omit provider to use the service default.",
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Beta access/rentals unavailable or insufficient credits. Starting requires at least two minutes of credit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "202": {
            "description": "Admitted or replayed session.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Session"
                },
                "example": {
                  "id": "abc123def456",
                  "state": "provisioning",
                  "provider": "shlut",
                  "device": null,
                  "close_requested": null,
                  "cleanup_pending": false,
                  "cleanup_status": null,
                  "billing_mode": "metered",
                  "phone": null,
                  "watch_url": "https://viewer.example.com/opaque-read-only-link",
                  "screen": null,
                  "age_s": 0,
                  "ready_age_s": 0,
                  "idle_s": 0,
                  "expires_at": 1800000900,
                  "timeout_seconds": 900,
                  "request_key": "example-request-key-0001"
                }
              }
            }
          },
          "400": {
            "description": "Invalid creation request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Account capacity is full (session_limit), idempotency conflict, or your profile phone is held by a live session or still being saved (profile_running).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "This keyed create already completed cleanup.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "All phones are in use (`code: phones_busy`, `retry_after_seconds` and a `Retry-After` header; no session was created, retry after the delay), or admission was recorded but the runtime is unavailable (`code: create_request_recorded`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/sessions/create-session"
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{16,128}$"
            },
            "description": "Same key plus same parsed JSON body reuses the session. Optional but recommended."
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSession"
              },
              "example": {
                "timeout_seconds": 900
              }
            }
          }
        }
      },
      "get": {
        "summary": "List active sessions",
        "operationId": "list-sessions",
        "tags": [
          "Sessions"
        ],
        "description": "Return this account's sessions known to the live runtime, including provisioning, error, or closing entries. Completed sessions belong in history.",
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account access denied or rental access unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Session array.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Session"
                  }
                },
                "example": [
                  {
                    "id": "abc123def456",
                    "state": "ready",
                    "provider": "shlut",
                    "device": "Android",
                    "close_requested": null,
                    "cleanup_pending": false,
                    "cleanup_status": null,
                    "billing_mode": "metered",
                    "phone": null,
                    "watch_url": "https://viewer.example.com/opaque-read-only-link",
                    "screen": {
                      "w": 720,
                      "h": 1280
                    },
                    "age_s": 150,
                    "ready_age_s": 10,
                    "idle_s": 0,
                    "ops": [
                      "screen.capture",
                      "screen.bounds",
                      "nav.home",
                      "input.tap",
                      "apps.list"
                    ],
                    "expires_at": 1800000900,
                    "timeout_seconds": 900
                  }
                ]
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/sessions/list-sessions"
        }
      }
    },
    "/sessions/{sid}": {
      "get": {
        "summary": "Get a session",
        "operationId": "get-session",
        "tags": [
          "Sessions"
        ],
        "description": "Inspect state, deadline, geometry and advertised operations. A valid read-only watch grant may read only its own session. Ready does not mean a browser frame has been displayed.",
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account access denied or rental access unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Session metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Session"
                },
                "example": {
                  "id": "abc123def456",
                  "state": "ready",
                  "provider": "shlut",
                  "device": "Android",
                  "close_requested": null,
                  "cleanup_pending": false,
                  "cleanup_status": null,
                  "billing_mode": "metered",
                  "phone": null,
                  "watch_url": "https://viewer.example.com/opaque-read-only-link",
                  "screen": {
                    "w": 720,
                    "h": 1280
                  },
                  "age_s": 150,
                  "ready_age_s": 10,
                  "idle_s": 0,
                  "expires_at": 1800000900,
                  "timeout_seconds": 900,
                  "adb": {
                    "host": "live.phone-harness.com",
                    "port": 22220,
                    "code": "ph_mfwcc5nknqaod034nwojzw0lzq",
                    "expires_at": 1800000900
                  }
                }
              }
            }
          },
          "404": {
            "description": "No live session visible to the caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/sessions/get-session"
        },
        "parameters": [
          {
            "name": "sid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Session ID returned by POST /sessions."
          }
        ],
        "security": [
          {
            "ApiKey": []
          },
          {
            "ViewGrant": []
          }
        ]
      },
      "delete": {
        "summary": "End a session",
        "operationId": "end-session",
        "tags": [
          "Sessions"
        ],
        "description": "Request closure of an owned session. New API operations stop and billable use ends at recorded closure. Capacity stays occupied until cleanup is confirmed. Repeating after the session is removed may return 404; use the create receipt to confirm cleanup.",
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account access denied or rental access unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Cleanup confirmed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Release"
                },
                "example": {
                  "released": true,
                  "cleanup_pending": false
                }
              }
            }
          },
          "202": {
            "description": "Closure accepted; cleanup is pending.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Release"
                },
                "example": {
                  "released": false,
                  "cleanup_pending": true
                }
              }
            }
          },
          "404": {
            "description": "No live session visible to the caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/sessions/end-session"
        },
        "parameters": [
          {
            "name": "sid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Session ID returned by POST /sessions."
          }
        ]
      }
    },
    "/sessions/requests/{request_key}": {
      "get": {
        "summary": "Recover a create request",
        "operationId": "recover-create-request",
        "tags": [
          "Sessions"
        ],
        "description": "Look up a durable, account-scoped create receipt, including after cleanup. A missing receipt does not justify retrying with a new key; reuse the same key and body for the same logical Start.",
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account access denied or rental access unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Durable receipt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateReceipt"
                },
                "example": {
                  "id": "abc123def456",
                  "request_key": "example-request-key-0001",
                  "provider": "shlut",
                  "state": "provisioning",
                  "cleanup_complete": false,
                  "cleanup_pending": false
                }
              }
            }
          },
          "404": {
            "description": "No receipt exists for this account and key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/sessions/recover-create-request"
        },
        "parameters": [
          {
            "name": "request_key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{16,128}$"
            },
            "description": "The original create idempotency key."
          }
        ]
      }
    },
    "/sessions/{sid}/frame.png": {
      "get": {
        "summary": "Get a snapshot",
        "operationId": "get-snapshot",
        "tags": [
          "Phone"
        ],
        "description": "Read a coalesced still image from a ready session. Captures are attempted at most once every two seconds; a cached image may be returned while an operation runs. This is not a live video endpoint and does not extend the session. Inspect Content-Type.",
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account access denied or rental access unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "PNG on the current Android service; compatible providers may return JPEG.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "404": {
            "description": "No live session visible to the caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Session is not ready, is closing, or the requested action conflicts with current state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "No frame available yet.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/phone/get-snapshot"
        },
        "parameters": [
          {
            "name": "sid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Session ID returned by POST /sessions."
          }
        ],
        "security": [
          {
            "ApiKey": []
          },
          {
            "ViewGrant": []
          }
        ]
      }
    },
    "/sessions/{sid}/owner-viewer": {
      "get": {
        "summary": "Get the owner viewer",
        "operationId": "get-owner-viewer",
        "tags": [
          "Phone"
        ],
        "description": "Mint an opaque, expiring interactive viewer URL for a ready, owned session. Treat it as a secret and never log it. Current embedding is restricted to the configured Phone Harness dashboard origin; arbitrary third-party embedding is not a supported integration. Reconnection does not extend the phone deadline. Watch grants cannot call this endpoint.",
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account access denied or rental access unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Owner viewer capability; private, no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OwnerViewer"
                },
                "example": {
                  "url": "https://viewer.example.com/opaque-owner-capability",
                  "expires_at": 1800000900
                }
              }
            }
          },
          "404": {
            "description": "No live session visible to the caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Session is not ready, is closing, or the requested action conflicts with current state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/phone/get-owner-viewer"
        },
        "parameters": [
          {
            "name": "sid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Session ID returned by POST /sessions."
          }
        ]
      }
    },
    "/sessions/{sid}/viewer": {
      "get": {
        "summary": "Get the live view",
        "operationId": "get-viewer",
        "tags": [
          "Phone"
        ],
        "description": "Mint an opaque, expiring view-only viewer URL for a ready session: the same live video as the owner viewer, without input. This is what a watch link opens. The session owner may call it with a bearer credential; a watch link's holder passes its view grant as the token query parameter. Treat the URL as a secret and never log it. Embedding is restricted to the Phone Harness dashboard origin. Reconnection does not extend the phone deadline.",
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account access denied or rental access unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "View-only viewer capability; private, no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OwnerViewer"
                },
                "example": {
                  "url": "https://viewer.example.com/opaque-view-capability?mode=view",
                  "expires_at": 1800000900
                }
              }
            }
          },
          "404": {
            "description": "No live session visible to the caller, or the session's provider has no live viewer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Session is not ready, is closing, or the requested action conflicts with current state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/phone/get-viewer"
        },
        "parameters": [
          {
            "name": "sid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Session ID returned by POST /sessions."
          },
          {
            "name": "token",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The session's view grant from its watch_url. Used instead of a bearer credential by watch link holders."
          }
        ],
        "security": [
          {
            "ApiKey": []
          },
          {}
        ]
      }
    },
    "/me": {
      "get": {
        "summary": "Get your account",
        "operationId": "get-account",
        "tags": [
          "Account"
        ],
        "description": "Read account identity, credits and account capacity. can_rent describes the rental-access gate only; balance and shared capacity are checked separately when creating a session.",
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account access denied or rental access unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                },
                "example": {
                  "uid": "exampleuser1",
                  "email": "developer@example.com",
                  "name": "Example developer",
                  "created": 1800000000,
                  "balance_cents": 1000,
                  "price_cents_per_minute": 35,
                  "environment": "production",
                  "billing_mode": "metered",
                  "active_session_billing_mode": null,
                  "session_create_idempotency": "v1",
                  "default_provider": "shlut",
                  "session_limit": 2,
                  "active_session_count": 0,
                  "available_session_slots": 2,
                  "can_rent": true,
                  "profile": {
                    "id": "prof-3f9c2a1b7d5e4c6a8b0f1e2d",
                    "state": "stored",
                    "session": null,
                    "exact": true,
                    "saved_at": 1800000600
                  }
                }
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/account/get-account"
        }
      }
    },
    "/me/profile/reset": {
      "post": {
        "summary": "Reset your phone",
        "operationId": "reset-profile",
        "tags": [
          "Account"
        ],
        "description": "Forget the saved profile phone: its apps, logins and data. The next session started with your profile_id begins with a fresh phone. Refused while a session holds the phone.",
        "responses": {
          "200": {
            "description": "Reset outcome. reset is false when nothing was saved.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "reset": {
                      "type": "boolean"
                    }
                  }
                },
                "example": {
                  "id": "prof-3f9c2a1b7d5e4c6a8b0f1e2d",
                  "reset": true
                }
              }
            }
          },
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A live session holds the phone (code profile_running, session names it). Stop it first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/account/reset-profile"
        }
      }
    },
    "/me/keys": {
      "get": {
        "summary": "List API keys",
        "operationId": "list-api-keys",
        "tags": [
          "Account"
        ],
        "description": "List active keys for your account. Full bearer keys are never returned here.",
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account access denied or rental access unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Active key metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ApiKey"
                  }
                },
                "example": [
                  {
                    "key_hash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
                    "label": "QA automation",
                    "hint": "pck_examp…0000",
                    "created": 1800000000,
                    "revoked": null
                  }
                ]
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/account/list-api-keys"
        }
      },
      "post": {
        "summary": "Create an API key",
        "operationId": "create-api-key",
        "tags": [
          "Account"
        ],
        "description": "Create another account-wide key. The raw secret is shown only once. For your first key, use the authenticated dashboard. A key can manage other keys on the same account.",
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account access denied or rental access unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "201": {
            "description": "New raw bearer key; save it securely.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string",
                      "description": "The raw account bearer key, shown once."
                    }
                  },
                  "required": [
                    "key"
                  ]
                },
                "example": {
                  "key": "pck_REPLACE_WITH_YOUR_NEW_KEY"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/account/create-api-key"
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "A descriptive name for this key."
                  }
                }
              },
              "example": {
                "label": "QA automation"
              }
            }
          }
        }
      }
    },
    "/me/keys/revoke": {
      "post": {
        "summary": "Revoke an API key",
        "operationId": "revoke-api-key",
        "tags": [
          "Account"
        ],
        "description": "Revoke a key belonging to this account using its key_hash from the key list, not the raw key. Revoking a key does not end sessions. The response is idempotent and does not prove the identifier existed.",
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account access denied or rental access unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Revocation request processed.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revoked": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "required": [
                    "revoked"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/account/revoke-api-key"
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "key_hash": {
                    "type": "string",
                    "description": "Identifier from GET /me/keys.",
                    "pattern": "^[a-f0-9]{64}$"
                  }
                },
                "required": [
                  "key_hash"
                ]
              }
            }
          }
        }
      }
    },
    "/history/page": {
      "get": {
        "summary": "List session history",
        "operationId": "list-history-page",
        "tags": [
          "History"
        ],
        "description": "Paginated finished-session records ordered by ended time, then session ID, descending. Cursors are opaque and account-bound. History end time is not cleanup confirmation; use the create receipt for that.",
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account access denied or rental access unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "History page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoryPage"
                },
                "example": {
                  "items": [
                    {
                      "sid": "abc123def456",
                      "uid": "exampleuser1",
                      "provider": "shlut",
                      "device": "Android",
                      "phone": null,
                      "created": 1800000000,
                      "ready": 1800000140,
                      "last": 1800000200,
                      "ended": 1800000230,
                      "end_reason": "released",
                      "billing_mode": "metered",
                      "cost_cents": 70,
                      "usage_minutes": 2,
                      "billable_minutes": 2
                    }
                  ],
                  "next_cursor": null
                }
              }
            }
          },
          "400": {
            "description": "Invalid limit, cursor, or query parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/history/list-history-page"
        },
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 40
            },
            "description": "Page size."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "The previous next_cursor, passed unchanged."
          }
        ]
      }
    },
    "/history": {
      "get": {
        "summary": "Get recent history",
        "operationId": "get-recent-history",
        "tags": [
          "History"
        ],
        "description": "Return up to 100 finished sessions, ordered by creation time descending. This endpoint has no pagination parameters. Prefer GET /history/page for a complete history.",
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account access denied or rental access unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Recent history array.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/HistoryItem"
                  }
                },
                "example": [
                  {
                    "sid": "abc123def456",
                    "uid": "exampleuser1",
                    "provider": "shlut",
                    "device": "Android",
                    "phone": null,
                    "created": 1800000000,
                    "ready": 1800000140,
                    "last": 1800000200,
                    "ended": 1800000230,
                    "end_reason": "released",
                    "billing_mode": "metered",
                    "cost_cents": 70,
                    "usage_minutes": 2,
                    "billable_minutes": 2
                  }
                ]
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/history/get-recent-history"
        }
      }
    },
    "/health": {
      "get": {
        "summary": "Check API liveness",
        "operationId": "get-health",
        "tags": [
          "Service"
        ],
        "description": "Public API-process liveness only. Does not certify phone availability, credits, capacity, video, or successful provisioning.",
        "responses": {
          "200": {
            "description": "API is responding.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "required": [
                    "ok"
                  ]
                },
                "example": {
                  "ok": true
                }
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/service/get-health"
        },
        "security": []
      }
    },
    "/sessions/{sid}/adb": {
      "post": {
        "summary": "Enable ADB",
        "operationId": "enable-adb",
        "tags": [
          "ADB"
        ],
        "description": "Return the ADB endpoint and unlock code for one ready session owned by your account. Ready phones already have ADB enabled and carry the same values in the session's adb block, so this is only needed to re-enable after DELETE; it is idempotent. Takes no body. Watch tokens cannot enable access. See the [connection guide](/guides/connect-adb).",
        "parameters": [
          {
            "name": "sid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Session ID returned by POST /sessions."
          }
        ],
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account access denied or rental access unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No live session visible to the caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Session is not ready, is closing, or the requested action conflicts with current state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "400": {
            "description": "Invalid registration, or ADB is unavailable on the session provider. Unavailable ADB includes unsupported: true.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdbConnection"
                },
                "example": {
                  "enabled": true,
                  "transport": "adb",
                  "host": "live.phone-harness.com",
                  "port": 22220,
                  "code": "ph_mfwcc5nknqaod034nwojzw0lzq",
                  "expires_at": 1800000900
                }
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/adb/enable-adb"
        }
      },
      "get": {
        "summary": "Get ADB access",
        "operationId": "get-adb",
        "tags": [
          "ADB"
        ],
        "description": "Return the ADB endpoint and unlock code for enabled ADB access, or enabled: false. Requires a ready, unexpired session owned by your account. Watch tokens cannot read this endpoint.",
        "parameters": [
          {
            "name": "sid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Session ID returned by POST /sessions."
          }
        ],
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account access denied or rental access unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No live session visible to the caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Session is not ready, is closing, or the requested action conflicts with current state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "400": {
            "description": "ADB is unavailable on this provider; response includes unsupported: true.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/AdbDisabled"
                    },
                    {
                      "$ref": "#/components/schemas/AdbConnection"
                    }
                  ]
                },
                "example": {
                  "enabled": true,
                  "transport": "adb",
                  "host": "live.phone-harness.com",
                  "port": 22220,
                  "code": "ph_mfwcc5nknqaod034nwojzw0lzq",
                  "expires_at": 1800000900
                }
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/adb/get-adb"
        }
      },
      "delete": {
        "summary": "Revoke ADB access",
        "operationId": "revoke-adb",
        "tags": [
          "ADB"
        ],
        "description": "Revoke ADB access for your session. This does not end the phone or stop its billing; use DELETE /sessions/{sid} to end the session. Revocation can be requested while a live session is closing or its deadline has passed, provided its allocation is still available. An already-removed session returns 404. Watch tokens cannot revoke access.",
        "parameters": [
          {
            "name": "sid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Session ID returned by POST /sessions."
          }
        ],
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account access denied or rental access unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No live session visible to the caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The session allocation is unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "400": {
            "description": "ADB is unavailable on this provider; response includes unsupported: true.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "200": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdbRevocation"
                },
                "example": {
                  "revoked": true
                }
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/adb/revoke-adb"
        }
      }
    },
    "/sessions/{sid}/adb/reset": {
      "post": {
        "summary": "Reset the unlock code",
        "operationId": "reset-adb",
        "tags": [
          "ADB"
        ],
        "description": "Mint a new unlock code for this session and close every existing ADB connection. Use it when the code has been shared too widely. The endpoint's host and port stay the same. Takes no body. Watch tokens cannot reset.",
        "parameters": [
          {
            "name": "sid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Session ID returned by POST /sessions."
          }
        ],
        "responses": {
          "401": {
            "description": "Bad or missing bearer credential.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Account access denied or rental access unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No live session visible to the caller.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Session is not ready, is closing, or the requested action conflicts with current state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected service error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "400": {
            "description": "Invalid registration, or ADB is unavailable on the session provider. Unavailable ADB includes unsupported: true.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "201": {
            "description": "Success.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdbConnection"
                },
                "example": {
                  "enabled": true,
                  "transport": "adb",
                  "host": "live.phone-harness.com",
                  "port": 22220,
                  "code": "ph_mfwcc5nknqaod034nwojzw0lzq",
                  "expires_at": 1800000900
                }
              }
            }
          }
        },
        "x-mint": {
          "href": "/api-reference/adb/reset-adb"
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Phone Harness account API key (pck_…). The dashboard may use a Clerk session JWT with the same header. Never put an account key in a URL."
      },
      "ViewGrant": {
        "type": "apiKey",
        "in": "query",
        "name": "token",
        "description": "Limited token from an existing watch URL. Reads only its one session metadata or snapshot; cannot control or install. Do not use an account API key here."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable public error."
          },
          "code": {
            "type": "string",
            "description": "Present only for errors with a stable code."
          },
          "state": {
            "type": "string"
          },
          "unsupported": {
            "type": "boolean"
          },
          "outcome": {
            "type": "string",
            "enum": [
              "unknown",
              "installed"
            ]
          },
          "id": {
            "type": "string",
            "description": "Session ID if a create receipt exists."
          },
          "request_key": {
            "type": "string"
          },
          "cleanup_complete": {
            "type": "boolean"
          },
          "balance_cents": {
            "type": "integer"
          },
          "session": {
            "type": "string"
          },
          "session_limit": {
            "type": "integer"
          },
          "active_session_count": {
            "type": "integer"
          },
          "available_session_slots": {
            "type": "integer"
          }
        },
        "required": [
          "error"
        ]
      },
      "Bounds": {
        "type": "object",
        "properties": {
          "x": {
            "type": "integer",
            "description": "Device pixels."
          },
          "y": {
            "type": "integer",
            "description": "Device pixels."
          },
          "w": {
            "type": "integer",
            "description": "Device pixels."
          },
          "h": {
            "type": "integer",
            "description": "Device pixels."
          }
        },
        "required": [
          "x",
          "y",
          "w",
          "h"
        ]
      },
      "ScreenSize": {
        "type": "object",
        "properties": {
          "w": {
            "type": "integer",
            "description": "Width in device pixels.",
            "minimum": 1
          },
          "h": {
            "type": "integer",
            "description": "Height in device pixels.",
            "minimum": 1
          }
        },
        "required": [
          "w",
          "h"
        ]
      },
      "StartupObservation": {
        "type": "object",
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "cold",
              "prepared"
            ]
          },
          "pool": {
            "type": "string",
            "enum": [
              "miss",
              "ineligible",
              "disabled",
              "hit"
            ]
          },
          "preparation_ms": {
            "type": "number",
            "description": "Optional preparation duration in milliseconds."
          },
          "inventory_age_ms": {
            "type": "number",
            "description": "Optional inventory age in milliseconds."
          }
        },
        "required": [
          "mode",
          "pool"
        ],
        "description": "Optional startup provenance. Presence is not a latency measurement or a guarantee that prepared starts are available."
      },
      "Session": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Opaque session ID."
          },
          "profile": {
            "type": [
              "string",
              "null"
            ],
            "description": "The profile id this session holds, or null for a temporary phone."
          },
          "state": {
            "type": "string",
            "enum": [
              "provisioning",
              "ready",
              "error",
              "closing"
            ]
          },
          "progress": {
            "type": "object",
            "description": "Only while `state` is `provisioning`: where the phone is. `queued` means every boot slot is busy and the phone starts as soon as one frees; `booting` means the phone is starting. `eta_seconds` is a typical remaining time from recent boots on this worker, not a promise.",
            "properties": {
              "phase": {
                "type": "string",
                "enum": [
                  "queued",
                  "booting"
                ]
              },
              "queue_position": {
                "type": "integer",
                "minimum": 1,
                "description": "Place in line, 1 = next. Present while `phase` is `queued`."
              },
              "eta_seconds": {
                "type": "integer",
                "minimum": 0,
                "description": "Typical seconds until the phone is ready."
              }
            },
            "required": [
              "phase"
            ]
          },
          "provider": {
            "type": "string",
            "description": "Provider selected for this session."
          },
          "device": {
            "type": [
              "string",
              "null"
            ],
            "description": "Display label; not a device connection address."
          },
          "close_requested": {
            "type": [
              "number",
              "null"
            ],
            "description": "Time closure was requested. Unix time in seconds."
          },
          "cleanup_pending": {
            "type": "boolean"
          },
          "cleanup_status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "complete",
              "allocation_uncertain",
              "pending",
              null
            ]
          },
          "billing_mode": {
            "type": "string",
            "enum": [
              "metered",
              "disabled"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Legacy saved-phone ID. Null for a new disposable session."
          },
          "watch_url": {
            "type": "string",
            "description": "Sensitive read-only snapshot link. Not the interactive owner viewer.",
            "format": "uri"
          },
          "screen": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/ScreenSize"
              },
              {
                "type": "null"
              }
            ]
          },
          "age_s": {
            "type": "integer",
            "description": "Rounded seconds since creation."
          },
          "ready_age_s": {
            "type": "integer",
            "description": "Rounded seconds since readiness, stopped at closure; zero before readiness."
          },
          "idle_s": {
            "type": "integer",
            "description": "Rounded seconds since the last activity recorded by the API. Does not extend or replace a selected session deadline."
          },
          "error": {
            "type": "string",
            "description": "Public startup failure, if present."
          },
          "expires_at": {
            "type": "number",
            "description": "Absolute session expiry, including provisioning. Unix time in seconds."
          },
          "timeout_seconds": {
            "type": "integer",
            "description": "Selected finite duration from creation, in seconds.",
            "minimum": 1
          },
          "startup": {
            "$ref": "#/components/schemas/StartupObservation"
          },
          "request_key": {
            "type": "string",
            "description": "Returned in a creation response when an idempotency key was supplied."
          },
          "adb": {
            "$ref": "#/components/schemas/SessionAdb",
            "nullable": true
          }
        },
        "required": [
          "id",
          "state",
          "provider",
          "device",
          "close_requested",
          "cleanup_pending",
          "cleanup_status",
          "billing_mode",
          "phone",
          "watch_url",
          "screen",
          "age_s",
          "ready_age_s",
          "idle_s"
        ]
      },
      "CreateSession": {
        "type": "object",
        "properties": {
          "profile_id": {
            "type": "string",
            "description": "Your account's profile id, from GET /me. The session then uses your own phone: its apps, logins and screen are saved when the session ends and restored for the next session started with the same profile_id. Only one session at a time may hold it, including the save after a session ends (409 profile_running). Omit for a temporary phone."
          },
          "timeout_seconds": {
            "type": "integer",
            "description": "Total session lifetime from admission, including provisioning. Omitting it uses 300 seconds. No unlimited mode; must fit a supported absolute timestamp.",
            "minimum": 1,
            "default": 300
          },
          "provider": {
            "type": "string",
            "description": "Optional provider selector. Omit it to use the service default. The current production service uses shlut.",
            "enum": [
              "shlut"
            ]
          }
        }
      },
      "CreateReceipt": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "request_key": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "enum": [
              "provisioning",
              "ready",
              "error",
              "closing",
              "recorded",
              "released"
            ]
          },
          "cleanup_complete": {
            "type": "boolean"
          },
          "cleanup_pending": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "request_key",
          "provider",
          "state",
          "cleanup_complete",
          "cleanup_pending"
        ]
      },
      "Release": {
        "type": "object",
        "properties": {
          "released": {
            "type": "boolean",
            "description": "True only when cleanup was confirmed by this response."
          },
          "cleanup_pending": {
            "type": "boolean"
          }
        },
        "required": [
          "released",
          "cleanup_pending"
        ]
      },
      "OwnerViewer": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "Opaque sensitive viewer capability. Current embedding is restricted to the configured Phone Harness dashboard origin.",
            "format": "uri"
          },
          "expires_at": {
            "type": "number",
            "description": "Viewer expiry; no later than the session expiry. Unix time in seconds."
          }
        },
        "required": [
          "url",
          "expires_at"
        ]
      },
      "Account": {
        "type": "object",
        "properties": {
          "uid": {
            "type": "string"
          },
          "profile": {
            "type": "object",
            "description": "Your own phone: one per account, kept between sessions.",
            "properties": {
              "id": {
                "type": "string",
                "description": "Pass this as profile_id when creating a session."
              },
              "state": {
                "type": "string",
                "enum": [
                  "empty",
                  "stored",
                  "running",
                  "saving"
                ],
                "description": "empty: nothing saved yet; stored: saved and ready to resume; running: held by a live session; saving: being stored after a session ended."
              },
              "session": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The live session holding the phone, if any."
              },
              "exact": {
                "type": [
                  "boolean",
                  "null"
                ],
                "description": "true when the saved phone resumes exactly as left; false when it will come back rebooted with its data intact."
              },
              "saved_at": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Unix seconds of the last save."
              }
            }
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "created": {
            "type": "number",
            "description": "Account creation. Unix time in seconds."
          },
          "balance_cents": {
            "type": "integer",
            "description": "US cents of available account credit."
          },
          "price_cents_per_minute": {
            "type": "integer",
            "description": "Current metered rate in US cents per minute."
          },
          "environment": {
            "type": "string"
          },
          "billing_mode": {
            "type": "string",
            "enum": [
              "metered",
              "disabled"
            ]
          },
          "active_session_billing_mode": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "metered",
              "disabled",
              null
            ]
          },
          "session_create_idempotency": {
            "type": "string",
            "enum": [
              "v1"
            ]
          },
          "default_provider": {
            "type": "string"
          },
          "session_limit": {
            "type": "integer",
            "description": "Account limit, not a service-wide capacity guarantee."
          },
          "active_session_count": {
            "type": "integer",
            "description": "Includes sessions pending cleanup."
          },
          "available_session_slots": {
            "type": "integer",
            "description": "Remaining account slots. Shared capacity is checked separately."
          },
          "can_rent": {
            "type": "boolean",
            "description": "Access gate only; balance and capacity are checked separately at creation."
          },
          "rent_blocked_reason": {
            "type": "string",
            "description": "Present if can_rent is false."
          }
        },
        "required": [
          "uid",
          "email",
          "name",
          "created",
          "balance_cents",
          "price_cents_per_minute",
          "environment",
          "billing_mode",
          "active_session_billing_mode",
          "session_create_idempotency",
          "default_provider",
          "session_limit",
          "active_session_count",
          "available_session_slots",
          "can_rent"
        ]
      },
      "ApiKey": {
        "type": "object",
        "properties": {
          "key_hash": {
            "type": "string",
            "description": "Key identifier used for revocation. This is not the raw bearer key.",
            "pattern": "^[a-f0-9]{64}$"
          },
          "label": {
            "type": [
              "string",
              "null"
            ]
          },
          "hint": {
            "type": "string",
            "description": "Non-secret display hint; cannot authenticate."
          },
          "created": {
            "type": "number",
            "description": "Key creation. Unix time in seconds."
          },
          "revoked": {
            "type": [
              "number",
              "null"
            ],
            "description": "Revocation time. Null in active key lists. Unix time in seconds."
          }
        },
        "required": [
          "key_hash",
          "label",
          "hint",
          "created",
          "revoked"
        ]
      },
      "HistoryItem": {
        "type": "object",
        "properties": {
          "sid": {
            "type": "string",
            "description": "Session ID; history uses sid rather than id."
          },
          "uid": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "device": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "created": {
            "type": "number",
            "description": "Session admission. Unix time in seconds."
          },
          "ready": {
            "type": [
              "number",
              "null"
            ],
            "description": "Readiness. Null if the session never became ready. Unix time in seconds."
          },
          "last": {
            "type": "number",
            "description": "Last recorded API activity. Unix time in seconds."
          },
          "ended": {
            "type": "number",
            "description": "Recorded end of the session, not proof of physical cleanup completion. Unix time in seconds."
          },
          "end_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "billing_mode": {
            "type": "string",
            "enum": [
              "metered",
              "disabled"
            ]
          },
          "cost_cents": {
            "type": "integer",
            "description": "Recorded credit debits for this session in US cents."
          },
          "usage_minutes": {
            "type": "integer",
            "description": "Ready-to-ended time rounded up to whole minutes."
          },
          "billable_minutes": {
            "type": "integer",
            "description": "Usage minutes for metered sessions; zero for disabled billing."
          }
        },
        "required": [
          "sid",
          "uid",
          "provider",
          "device",
          "phone",
          "created",
          "ready",
          "last",
          "ended",
          "end_reason",
          "billing_mode",
          "cost_cents",
          "usage_minutes",
          "billable_minutes"
        ]
      },
      "HistoryPage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/HistoryItem"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Opaque account-bound cursor. Null at the end."
          }
        },
        "required": [
          "items",
          "next_cursor"
        ]
      },
      "TreeNode": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string"
          },
          "desc": {
            "type": "string"
          },
          "id": {
            "type": "string",
            "description": "Android resource ID, not session ID."
          },
          "class": {
            "type": "string"
          },
          "clickable": {
            "type": "boolean"
          },
          "x": {
            "type": "integer",
            "description": "Horizontal center in device pixels."
          },
          "y": {
            "type": "integer",
            "description": "Vertical center in device pixels."
          },
          "w": {
            "type": "integer"
          },
          "h": {
            "type": "integer"
          }
        },
        "required": [
          "text",
          "desc",
          "id",
          "class",
          "clickable",
          "x",
          "y",
          "w",
          "h"
        ]
      },
      "TextNode": {
        "type": "object",
        "properties": {
          "text": {
            "type": "string"
          },
          "confidence": {
            "type": "number",
            "description": "Current hierarchy-derived value is 1.0; not an OCR score."
          },
          "source": {
            "type": "string",
            "enum": [
              "tree"
            ]
          },
          "x": {
            "type": "integer",
            "description": "Horizontal center."
          },
          "y": {
            "type": "integer",
            "description": "Vertical center."
          },
          "w": {
            "type": "integer"
          },
          "h": {
            "type": "integer"
          }
        },
        "required": [
          "text",
          "confidence",
          "source",
          "x",
          "y",
          "w",
          "h"
        ]
      },
      "Capture": {
        "type": "object",
        "properties": {
          "png_b64": {
            "type": "string",
            "description": "Base64-encoded PNG bytes."
          },
          "bounds": {
            "$ref": "#/components/schemas/Bounds"
          }
        },
        "required": [
          "png_b64",
          "bounds"
        ]
      },
      "AdbConnection": {
        "type": "object",
        "required": [
          "enabled",
          "transport",
          "host",
          "port",
          "code",
          "expires_at"
        ],
        "additionalProperties": false,
        "properties": {
          "enabled": {
            "type": "boolean",
            "const": true
          },
          "transport": {
            "type": "string",
            "const": "adb"
          },
          "host": {
            "type": "string",
            "description": "DNS name of the ADB endpoint. Use the returned value."
          },
          "port": {
            "type": "integer",
            "minimum": 1,
            "maximum": 65535,
            "description": "TCP port dedicated to this phone while the session lives. Run adb connect host:port."
          },
          "code": {
            "type": "string",
            "pattern": "^ph_[a-z2-7]{26}$",
            "description": "The unlock code. After connecting, run adb shell unlock <code>. Anyone who has host, port and code can drive the phone until the session ends or the code is reset."
          },
          "expires_at": {
            "type": "number",
            "description": "Unix timestamp in seconds when access expires. Never later than the session deadline; ADB access does not extend the session."
          }
        }
      },
      "AdbDisabled": {
        "type": "object",
        "required": [
          "enabled"
        ],
        "additionalProperties": false,
        "properties": {
          "enabled": {
            "type": "boolean",
            "const": false
          }
        }
      },
      "AdbRevocation": {
        "type": "object",
        "required": [
          "revoked"
        ],
        "additionalProperties": false,
        "properties": {
          "revoked": {
            "type": "boolean",
            "const": true
          }
        }
      },
      "SessionAdb": {
        "type": "object",
        "required": [
          "host",
          "port",
          "code",
          "expires_at"
        ],
        "additionalProperties": false,
        "properties": {
          "host": {
            "type": "string",
            "description": "DNS name of the ADB endpoint. Use the returned value."
          },
          "port": {
            "type": "integer",
            "minimum": 1,
            "maximum": 65535,
            "description": "TCP port dedicated to this phone while the session lives. Run adb connect host:port."
          },
          "code": {
            "type": "string",
            "pattern": "^ph_[a-z2-7]{26}$",
            "description": "The unlock code. After connecting, run adb shell unlock <code>. Anyone who has host, port and code can drive the phone until the session ends or the code is reset."
          },
          "expires_at": {
            "type": "number",
            "description": "Unix timestamp in seconds when access expires. Never later than the session deadline; ADB access does not extend the session."
          }
        },
        "description": "Present once the session is ready and ADB is enabled."
      }
    }
  }
}
