> ## Documentation Index
> Fetch the complete documentation index at: https://phone-harness.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Create a session

> 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.



## OpenAPI

````yaml /openapi.json post /sessions
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:
      tags:
        - Sessions
      summary: Create a session
      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.
      operationId: 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
      responses:
        '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'
        '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'
        '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'
        '500':
          description: Unexpected service error.
          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'
components:
  schemas:
    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
    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
    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
    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.
    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.
  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.

````