{
  "openapi": "3.0.3",
  "info": {
    "title": "Mobile Next Cloud API",
    "description": "REST API for Mobile Next Cloud. All endpoints require a Bearer token starting with mob_. API keys are service keys bound to one organization; every request acts on that organization.",
    "version": "2.1.0"
  },
  "servers": [
    {
      "url": "https://app.mobilenext.ai",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Account"
    },
    {
      "name": "Organizations"
    },
    {
      "name": "Members"
    },
    {
      "name": "Invitations"
    },
    {
      "name": "API Keys"
    },
    {
      "name": "Sessions"
    },
    {
      "name": "Instances"
    },
    {
      "name": "Devices"
    },
    {
      "name": "Chats"
    },
    {
      "name": "Billing"
    },
    {
      "name": "Test Results"
    },
    {
      "name": "Files",
      "description": "Preview — may change without notice. Stored app binaries (.apk, .ipa), deduplicated per organization by SHA-256, reusable across devices and sessions until they expire."
    },
    {
      "name": "Apps",
      "description": "Preview — may change without notice. Stored files grouped by organization, platform and bundle id. Created by the server when a file becomes ready; read-only."
    }
  ],
  "paths": {
    "/api/v1/me": {
      "get": {
        "summary": "Get current user",
        "operationId": "getMe",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "Current user info",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "delete": {
        "summary": "Delete your account",
        "description": "Deletes the caller's account and their Personal organization. Every session, API key and CLI login stops working, and the same login can sign up again later as a new account. Refused while the caller still belongs to any other organization: leave or delete those first. Not available to API keys.",
        "operationId": "deleteMe",
        "tags": [
          "Account"
        ],
        "responses": {
          "204": {
            "description": "Account deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/keys": {
      "get": {
        "summary": "List API keys",
        "description": "Lists all API keys in the selected organization, visible to every member of it. Secrets are never returned; only a short suffix is shown to help identify each key. Requires an interactive (JWT) session: an API key cannot list keys. Use the X-Organization-Id header to choose the organization; otherwise the caller's default organization is used.",
        "operationId": "listApiKeys",
        "tags": [
          "API Keys"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "All API keys in the selected organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "summary": "Create an API key",
        "description": "Creates a new API key in the selected organization and returns its secret. The full secret is shown only once, in this response; it is never retrievable again.\n\nRequires an interactive (JWT) session: an API key cannot create another API key. Use the X-Organization-Id header to choose which organization the new key belongs to.",
        "operationId": "createApiKey",
        "tags": [
          "API Keys"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateApiKeyRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "API key created; the full secret is returned once in apiKey",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedApiKey"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Caller used an API key (not an interactive JWT session) to create a key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/keys/{id}": {
      "delete": {
        "summary": "Delete an API key",
        "description": "Permanently deletes an API key in the organization. Any key in the organization can be deleted (not only the caller's own). Requires an interactive (JWT) session: an API key cannot delete keys. Requires a read/write or admin role; read-only members cannot delete keys. Use the X-Organization-Id header to choose the organization; otherwise the caller's default organization is used.",
        "operationId": "deleteApiKey",
        "tags": [
          "API Keys"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "API key id",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "API key deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/invitations": {
      "get": {
        "summary": "List invitations addressed to the caller",
        "description": "Pending, unexpired invitations addressed to the caller's email across all organizations. Interactive (JWT) sessions only.",
        "operationId": "listIncomingInvitations",
        "tags": [
          "Invitations"
        ],
        "responses": {
          "200": {
            "description": "Pending invitations addressed to the caller",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IncomingInvitationList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/api/v1/invitations/{invitationId}/accept": {
      "post": {
        "summary": "Accept an invitation",
        "description": "Accepts an invitation addressed to the caller, joining the organization with the invited role. Interactive (JWT) sessions only.",
        "operationId": "acceptInvitation",
        "tags": [
          "Invitations"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/invitationId"
          }
        ],
        "responses": {
          "204": {
            "description": "Invitation accepted; the caller is now a member of the organization"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/invitations/{invitationId}/reject": {
      "post": {
        "summary": "Reject an invitation",
        "description": "Rejects (declines) an invitation addressed to the caller. Interactive (JWT) sessions only.",
        "operationId": "rejectInvitation",
        "tags": [
          "Invitations"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/invitationId"
          }
        ],
        "responses": {
          "204": {
            "description": "Invitation rejected"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/organizations": {
      "get": {
        "summary": "List organizations the caller belongs to",
        "operationId": "listOrganizations",
        "tags": [
          "Organizations"
        ],
        "responses": {
          "200": {
            "description": "Organizations the caller is a member of, with their role in each",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrganizationList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "post": {
        "summary": "Create a new organization",
        "description": "Creates an organization owned by the caller (who becomes its admin). The name is required, cannot be \"Personal\", and cannot duplicate the name of an organization the caller already belongs to.",
        "operationId": "createOrganization",
        "tags": [
          "Organizations"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOrganizationRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Organization created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Organization"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/organizations/{id}/members": {
      "get": {
        "summary": "List organization members",
        "description": "Returns the active members of the organization. The caller must be a member of the organization.",
        "operationId": "listOrganizationMembers",
        "tags": [
          "Members"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationPathId"
          }
        ],
        "responses": {
          "200": {
            "description": "Active members of the organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemberList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/api/v1/organizations/{id}/invitations": {
      "get": {
        "summary": "List pending invitations for the organization",
        "description": "Returns the pending invitations for the organization. Admin only.",
        "operationId": "listOrganizationInvitations",
        "tags": [
          "Invitations"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationPathId"
          }
        ],
        "responses": {
          "200": {
            "description": "Pending invitations for the organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemberInvitationList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "summary": "Invite a new member to the organization",
        "description": "Creates a pending invitation for an email with the given role. Admin only. Fails if the email already belongs to a member or already has a pending invitation.",
        "operationId": "inviteOrganizationMember",
        "tags": [
          "Invitations"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationPathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InviteMemberRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Invitation created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MemberInvitation"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/organizations/{id}/invitations/{invitationId}": {
      "delete": {
        "summary": "Revoke a pending invitation",
        "description": "Revokes a pending invitation in the organization. Admin only.",
        "operationId": "revokeOrganizationInvitation",
        "tags": [
          "Invitations"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationPathId"
          },
          {
            "$ref": "#/components/parameters/invitationId"
          }
        ],
        "responses": {
          "204": {
            "description": "Invitation revoked"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/billing/balance": {
      "get": {
        "summary": "Get plan and remaining usage",
        "description": "Returns the selected organization's plan, its overage cap, its Stripe subscription state (null on the free hobby plan), and the remaining seconds for each metered resource. Usage is metered in seconds, not money: a plan grants a number of seconds per period and sessions debit them. On a paid plan secondsRemaining may be negative, which is overage billed at the end of the period up to overageCapUsd. Visible to every member of the organization.",
        "operationId": "getBillingBalance",
        "tags": [
          "Billing"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "Current plan, subscription, and usage balances",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Balance"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/sessions": {
      "get": {
        "summary": "List sessions",
        "description": "Sessions in the selected organization, visible to every member of it, newest first, with cursor pagination. The organization is chosen by the X-Organization-Id header, or the organization the API key is bound to, otherwise the caller's default organization.",
        "operationId": "listSessions",
        "tags": [
          "Sessions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum sessions to return (default 100, max 500)",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "before",
            "in": "query",
            "required": false,
            "description": "Cursor: return sessions created strictly before this RFC3339 timestamp",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Only sessions created in the last N days (1-90). Combines with before.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 90
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of sessions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "summary": "Create a session",
        "description": "Creates an empty session container. Attach resources to it separately: use fleet.allocate with sessionId to link a device, or allocate an instance with sessionId.",
        "operationId": "createSession",
        "tags": [
          "Sessions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "responses": {
          "201": {
            "description": "Session created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Session"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/api/v1/sessions/{sessionId}": {
      "get": {
        "summary": "Get a session",
        "description": "Returns a session in the selected organization, visible to every member of it. The organization is chosen by the X-Organization-Id header, or the organization the API key is bound to, otherwise the caller's default organization.",
        "operationId": "getSession",
        "tags": [
          "Sessions"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          }
        ],
        "responses": {
          "200": {
            "description": "Session detail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Session"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sessions/{sessionId}/instances": {
      "get": {
        "summary": "List instances in a session",
        "description": "Instances attached to the session, newest first. Visible to every member of the organization that owns the session.",
        "operationId": "listInstances",
        "tags": [
          "Instances"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          }
        ],
        "responses": {
          "200": {
            "description": "Instances attached to the session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InstanceList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "summary": "Allocate an instance",
        "description": "Allocates a new instance (a build/automation machine) attached to the session. Provisioning is asynchronous: the instance is returned immediately with status \"provisioning\" and becomes usable once its status is \"ready\". Poll GET /sessions/{sessionId}/instances/{instanceId} to observe readiness.",
        "operationId": "createInstance",
        "tags": [
          "Instances"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateInstanceRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Instance allocated; provisioning has started",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Instance"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Session not found in the selected organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Instance concurrency quota reached; release an active instance before allocating another",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sessions/{sessionId}/instances/{instanceId}": {
      "get": {
        "summary": "Get an instance",
        "description": "Returns an instance attached to the session. Visible to every member of the organization that owns the session.",
        "operationId": "getInstance",
        "tags": [
          "Instances"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          },
          {
            "$ref": "#/components/parameters/instanceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Instance detail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Instance"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sessions/{sessionId}/instances/{instanceId}/release": {
      "post": {
        "summary": "Release an instance",
        "description": "Releases an instance, stopping billing and tearing it down. Returns the instance with its updated status. Idempotent: releasing an already-released instance is a no-op.",
        "operationId": "releaseInstance",
        "tags": [
          "Instances"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          },
          {
            "$ref": "#/components/parameters/instanceId"
          }
        ],
        "responses": {
          "202": {
            "description": "Release accepted; teardown proceeds asynchronously",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Instance"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sessions/{sessionId}/devices": {
      "get": {
        "summary": "List devices in a session",
        "description": "Devices attached to the session, newest first. Visible to every member of the organization that owns the session.",
        "operationId": "listDevices",
        "tags": [
          "Devices"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          }
        ],
        "responses": {
          "200": {
            "description": "Devices attached to the session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeviceList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "summary": "Allocate a device",
        "description": "Allocates a device matching the given filters and attaches it to the session. Uses the same filter DSL as the fleet.allocate RPC. Allocation is asynchronous: the device is returned immediately with status \"provisioning\" or \"queued\" and becomes usable once its status is \"ready\". Poll GET /sessions/{sessionId}/devices/{deviceId} to observe readiness.",
        "operationId": "allocateDevice",
        "tags": [
          "Devices"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AllocateDeviceRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Device allocated; provisioning has started",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionDevice"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Session not found in the selected organization — also file_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Device concurrency quota reached; release an active device before allocating another",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sessions/{sessionId}/devices/{deviceId}": {
      "get": {
        "summary": "Get a device",
        "description": "Returns a device attached to the session. Visible to every member of the organization that owns the session.",
        "operationId": "getDevice",
        "tags": [
          "Devices"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          },
          {
            "$ref": "#/components/parameters/deviceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Device detail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionDevice"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sessions/{sessionId}/devices/{deviceId}/release": {
      "post": {
        "summary": "Release a device",
        "description": "Releases a device, stopping billing and returning it to the fleet. Returns the device with its updated status. Idempotent: releasing an already-released device is a no-op.",
        "operationId": "releaseDevice",
        "tags": [
          "Devices"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          },
          {
            "$ref": "#/components/parameters/deviceId"
          }
        ],
        "responses": {
          "202": {
            "description": "Release accepted; teardown proceeds asynchronously",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionDevice"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sessions/{sessionId}/chats": {
      "get": {
        "summary": "List chats in a session",
        "description": "Chats belonging to the session, oldest first. A chat exists only inside a session; there is no way to address one without it. Visible to every member of the organization that owns the session.",
        "operationId": "listChats",
        "tags": [
          "Chats"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          }
        ],
        "responses": {
          "200": {
            "description": "Chats in the session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "summary": "Create a chat",
        "description": "Creates a chat in the session. Bind the chat to an allocated device by setting metadata.deviceId; the assistant's device tools then act on that device. Every action is still authorized against the caller, never against the bound device.",
        "operationId": "createChat",
        "tags": [
          "Chats"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateChatRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Chat created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Chat"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "Session not found in the selected organization",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sessions/{sessionId}/chats/{chatId}": {
      "get": {
        "summary": "Get a chat",
        "description": "Returns a chat in the session. A chat id that belongs to a different session is not found, even within the same organization.",
        "operationId": "getChat",
        "tags": [
          "Chats"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          },
          {
            "$ref": "#/components/parameters/chatId"
          }
        ],
        "responses": {
          "200": {
            "description": "Chat detail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Chat"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sessions/{sessionId}/chats/{chatId}/messages": {
      "get": {
        "summary": "List messages in a chat",
        "description": "The most recent messages of the chat, returned oldest first, with cursor pagination backwards into older history. Each assistant message includes the tool calls it made, so a client can rebuild the conversation — text and tool calls — after a page reload. Follow nextCursor with ?before= to load older messages.",
        "operationId": "listChatMessages",
        "tags": [
          "Chats"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          },
          {
            "$ref": "#/components/parameters/chatId"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum messages to return (default 100, max 500)",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            }
          },
          {
            "name": "before",
            "in": "query",
            "required": false,
            "description": "Cursor: return the messages created strictly before this RFC3339 timestamp. Pass the nextCursor of a previous page to walk further back through the conversation.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of messages, oldest first",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageList"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "summary": "Post a message and run the assistant",
        "description": "Appends a user message and runs the assistant. Three modes:\n\n- default (async): responds 202 with the assistant message in status \"running\". Follow execution.eventsUrl to stream it.\n- mode=sync: runs inline and responds 201 with the completed assistant message and its tool calls.\n- stream=true: responds with a text/event-stream of execution and assistant.message events.\n\nSet execute=false to store the user message without running the assistant.",
        "operationId": "createChatMessage",
        "tags": [
          "Chats"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          },
          {
            "$ref": "#/components/parameters/chatId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMessageRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "stream=true: a text/event-stream of execution.* and assistant.message.* events",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "201": {
            "description": "mode=sync, or execute=false: the stored message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "202": {
            "description": "async: the assistant message in status \"running\"; follow execution.eventsUrl",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sessions/{sessionId}/chats/{chatId}/messages/{messageId}": {
      "get": {
        "summary": "Get a message",
        "operationId": "getChatMessage",
        "tags": [
          "Chats"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          },
          {
            "$ref": "#/components/parameters/chatId"
          },
          {
            "$ref": "#/components/parameters/messageId"
          }
        ],
        "responses": {
          "200": {
            "description": "Message detail, including its tool calls",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sessions/{sessionId}/chats/{chatId}/messages/{messageId}/events": {
      "get": {
        "summary": "Stream a message's execution events",
        "description": "Server-Sent Events for one assistant message: execution.started, assistant.message.started, assistant.message.delta, tool_call.created, tool_call.completed, assistant.message.completed, execution.completed|failed.\n\nSafe to call after a disconnect: a still-running execution replays its events from the start, and a finished one replays its stored final state — the stored replay emits the message text as a single delta and no tool_call events. Clients must therefore not render the message's persisted text and also reattach here, or the text appears twice.",
        "operationId": "streamChatMessageEvents",
        "tags": [
          "Chats"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          },
          {
            "$ref": "#/components/parameters/chatId"
          },
          {
            "$ref": "#/components/parameters/messageId"
          }
        ],
        "responses": {
          "200": {
            "description": "An event stream for the message's execution",
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sessions/{sessionId}/chats/{chatId}/messages/{messageId}/tool-calls": {
      "get": {
        "summary": "List a message's tool calls",
        "operationId": "listChatMessageToolCalls",
        "tags": [
          "Chats"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          },
          {
            "$ref": "#/components/parameters/chatId"
          },
          {
            "$ref": "#/components/parameters/messageId"
          }
        ],
        "responses": {
          "200": {
            "description": "Tool calls made while producing the message",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolCallList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/sessions/{sessionId}/chats/{chatId}/messages/{messageId}/cancel": {
      "post": {
        "summary": "Cancel a running message",
        "description": "Requests cancellation of a message whose execution is still running. Requires a read/write or admin role.",
        "operationId": "cancelChatMessage",
        "tags": [
          "Chats"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/sessionId"
          },
          {
            "$ref": "#/components/parameters/chatId"
          },
          {
            "$ref": "#/components/parameters/messageId"
          }
        ],
        "responses": {
          "200": {
            "description": "The message with its updated status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Message"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/test-results": {
      "get": {
        "summary": "List test results",
        "description": "Test results in the selected organization, visible to every member of it. The organization is chosen by the X-Organization-Id header, or the organization the API key is bound to, otherwise the caller's default organization.",
        "operationId": "listTestResults",
        "tags": [
          "Test Results"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list of test results, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestResultList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "summary": "Create a test result",
        "description": "Called by the mobilewright reporter once a test run completes. Returns the created record including its id, which is used for subsequent asset uploads. The result is filed under the organization the API key is bound to, or the X-Organization-Id header, otherwise the caller's default organization.",
        "operationId": "createTestResult",
        "tags": [
          "Test Results"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTestResultRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Test result created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/test-results/{testResultId}": {
      "get": {
        "summary": "Get a test result",
        "operationId": "getTestResult",
        "tags": [
          "Test Results"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/testResultId"
          }
        ],
        "responses": {
          "200": {
            "description": "Test result detail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "summary": "Update a test result",
        "description": "Partial update, typically called by the reporter to finish a run that was created as queued or running. Only the fields present in the body change.",
        "operationId": "updateTestResult",
        "tags": [
          "Test Results"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/testResultId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTestResultRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated test result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/test-results/{testResultId}/assets": {
      "get": {
        "summary": "List assets for a test result",
        "description": "Returns all uploaded assets (e.g. trace.zip, screenshots.zip) for this test result.",
        "operationId": "listTestResultAssets",
        "tags": [
          "Test Results"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/testResultId"
          }
        ],
        "responses": {
          "200": {
            "description": "List of assets",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssetList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "summary": "Upload an asset",
        "description": "Uploads a binary asset attached to a test result. Use multipart/form-data with the file and its name. In future, a presignedUrl parameter may be added to return an S3 pre-signed URL instead of accepting the upload directly.",
        "operationId": "uploadTestResultAsset",
        "tags": [
          "Test Results"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/testResultId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "file"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Asset filename",
                    "example": "trace.zip"
                  },
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Asset uploaded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Asset"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/test-results/{testResultId}/assets/{assetId}": {
      "get": {
        "summary": "Download an asset",
        "description": "Streams the raw binary content of a specific asset.",
        "operationId": "downloadTestResultAsset",
        "tags": [
          "Test Results"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          },
          {
            "$ref": "#/components/parameters/testResultId"
          },
          {
            "$ref": "#/components/parameters/assetId"
          }
        ],
        "responses": {
          "200": {
            "description": "Raw asset content",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/organizations/{id}": {
      "patch": {
        "summary": "Rename an organization",
        "description": "Renames the organization. Admin only. Not available for the Personal organization, nor to API keys. The name cannot be blank, cannot be \"Personal\", and cannot be the name of another organization the caller belongs to.",
        "operationId": "renameOrganization",
        "tags": [
          "Organizations"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationPathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RenameOrganizationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Organization renamed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Organization"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      },
      "delete": {
        "summary": "Delete an organization",
        "description": "Deletes the organization. Its members and API keys can no longer use it. Admin only, and only once the caller is its sole member; refused while a plan still renews (a plan already set to cancel at period end does not block) or a device is still allocated. Not available for the Personal organization, nor to API keys.",
        "operationId": "deleteOrganization",
        "tags": [
          "Organizations"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationPathId"
          }
        ],
        "responses": {
          "204": {
            "description": "Organization deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/organizations/{id}/members/{userId}": {
      "patch": {
        "summary": "Change a member's role",
        "description": "Sets another member's role. Admin only. A caller can never change their own role: every change is made by an admin who stays an admin, so an organization cannot lose its last admin. Applies on the member's next request.",
        "operationId": "changeOrganizationMemberRole",
        "tags": [
          "Members"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationPathId"
          },
          {
            "$ref": "#/components/parameters/memberUserId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChangeMemberRoleRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Role changed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrganizationMember"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "summary": "Remove a member, or leave",
        "description": "Removes a member from the organization. When `userId` is the caller (or `me`) this is a leave, allowed to any member; otherwise the caller must be an admin. The last admin cannot leave: make another member an admin first, or delete the organization. The removed member's API keys for this organization stop working.",
        "operationId": "removeOrganizationMember",
        "tags": [
          "Members"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationPathId"
          },
          {
            "$ref": "#/components/parameters/memberUserId"
          }
        ],
        "responses": {
          "204": {
            "description": "Member removed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/api/v1/files": {
      "post": {
        "summary": "Create a file",
        "description": "Declares a file by its SHA-256. If the organization already stores that hash, the stored file is returned instead of a new one.\n\n**PUT the bytes if and only if the response contains `upload`.** Do not branch on the status code. Then call `completeFile` unless the file is already `ready`.\n\n| Stored file with this sha256 | Response | `upload` |\n|---|---|---|\n| none | `201`, `pendingUpload` | yes |\n| `pendingUpload` or `failed` | `200`, same id, reset to `pendingUpload` | yes, fresh URL |\n| `processing` | `200` | no |\n| `ready` | `200`, expiry extended, `lastUsedAt` set | no |\n\nA dedup hit keeps the first create's `filename`, `name`, `build` and `user`, and never shortens the expiry. Rate limited to 100 creates per 15 minutes per organization.",
        "operationId": "createFile",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateFileRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The organization already stores this sha256; `upload` is present only when the bytes are still needed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateFileResult"
                },
                "examples": {
                  "dedupHit": {
                    "summary": "Ready file, nothing to upload",
                    "value": {
                      "id": "2f1c8e4a-1f2b-4a1e-9a2c-0b7f5d3e6a11",
                      "app": null,
                      "platform": null,
                      "filename": "app-release.apk",
                      "filesize": 48211033,
                      "sha256": "9f2c0b7f5d3e6a1100000000000000000000000000000000000000000000e1a4",
                      "name": "app-release.apk",
                      "bundleId": null,
                      "version": null,
                      "buildNumber": null,
                      "minOsVersion": null,
                      "isSimulator": false,
                      "build": {
                        "branch": "fix/checkout",
                        "commit": "3dbd301"
                      },
                      "source": "api",
                      "organization": "8b0e2c1a-5d3f-4e7b-9a61-2c4d8f0a1b33",
                      "user": "github|1234",
                      "failureReason": null,
                      "createdAt": "2026-09-18T10:00:00Z",
                      "lastUsedAt": "2026-09-18T10:04:12Z",
                      "expiresAt": "2026-10-18T10:00:00Z",
                      "status": "ready"
                    }
                  },
                  "pendingRetry": {
                    "summary": "Earlier upload never completed; fresh URL",
                    "value": {
                      "id": "2f1c8e4a-1f2b-4a1e-9a2c-0b7f5d3e6a11",
                      "app": null,
                      "platform": null,
                      "filename": "app-release.apk",
                      "filesize": 48211033,
                      "sha256": "9f2c0b7f5d3e6a1100000000000000000000000000000000000000000000e1a4",
                      "name": "app-release.apk",
                      "bundleId": null,
                      "version": null,
                      "buildNumber": null,
                      "minOsVersion": null,
                      "isSimulator": false,
                      "build": {
                        "branch": "fix/checkout",
                        "commit": "3dbd301"
                      },
                      "source": "api",
                      "organization": "8b0e2c1a-5d3f-4e7b-9a61-2c4d8f0a1b33",
                      "user": "github|1234",
                      "failureReason": null,
                      "createdAt": "2026-09-18T10:00:00Z",
                      "lastUsedAt": null,
                      "expiresAt": "2026-10-18T10:00:00Z",
                      "status": "pendingUpload",
                      "upload": {
                        "method": "PUT",
                        "url": "https://mobilenexthq-app-files.s3.us-west-2.amazonaws.com/files/2f1c8e4a-1f2b-4a1e-9a2c-0b7f5d3e6a11?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Signature=abc123",
                        "headers": {
                          "X-Amz-Checksum-Sha256": "nywLf1PT5qEa…4aQ=",
                          "Content-Length": "48211033"
                        },
                        "expiresAt": "2026-09-18T12:00:00Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "New file; upload the bytes to `upload.url`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateFileResult"
                },
                "examples": {
                  "newUpload": {
                    "summary": "New file",
                    "value": {
                      "id": "2f1c8e4a-1f2b-4a1e-9a2c-0b7f5d3e6a11",
                      "app": null,
                      "platform": null,
                      "filename": "app-release.apk",
                      "filesize": 48211033,
                      "sha256": "9f2c0b7f5d3e6a1100000000000000000000000000000000000000000000e1a4",
                      "name": "app-release.apk",
                      "bundleId": null,
                      "version": null,
                      "buildNumber": null,
                      "minOsVersion": null,
                      "isSimulator": false,
                      "build": {
                        "branch": "fix/checkout",
                        "commit": "3dbd301"
                      },
                      "source": "api",
                      "organization": "8b0e2c1a-5d3f-4e7b-9a61-2c4d8f0a1b33",
                      "user": "github|1234",
                      "failureReason": null,
                      "createdAt": "2026-09-18T10:00:00Z",
                      "lastUsedAt": null,
                      "expiresAt": "2026-10-18T10:00:00Z",
                      "status": "pendingUpload",
                      "upload": {
                        "method": "PUT",
                        "url": "https://mobilenexthq-app-files.s3.us-west-2.amazonaws.com/files/2f1c8e4a-1f2b-4a1e-9a2c-0b7f5d3e6a11?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Signature=abc123",
                        "headers": {
                          "X-Amz-Checksum-Sha256": "nywLf1PT5qEa…4aQ=",
                          "Content-Length": "48211033"
                        },
                        "expiresAt": "2026-09-18T12:00:00Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request: invalid_body, invalid_filename, invalid_filesize, invalid_sha256, invalid_name, invalid_build",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "readwrite_required (read-only member), or storage_quota_exceeded (the organization would pass its storage limit)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "filesize_conflict: this sha256 was created with a different filesize",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "file_too_large: filesize is over the plan's per-file cap",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "unsupported_file_type (not .apk or .ipa), or expiry_out_of_range",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited: more than 100 creates in 15 minutes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "summary": "List files",
        "description": "The organization's unexpired files, newest first. Any role.",
        "operationId": "listFiles",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/sha256Query"
          },
          {
            "$ref": "#/components/parameters/appQuery"
          },
          {
            "$ref": "#/components/parameters/platformQuery"
          },
          {
            "$ref": "#/components/parameters/bundleIdQuery"
          },
          {
            "$ref": "#/components/parameters/branchQuery"
          },
          {
            "$ref": "#/components/parameters/commitQuery"
          },
          {
            "$ref": "#/components/parameters/fileStatusQuery"
          },
          {
            "$ref": "#/components/parameters/q"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of files",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request: unknown status or platform, limit out of range, malformed cursor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/files/usage": {
      "get": {
        "summary": "Get file storage usage",
        "description": "Stored bytes against the plan's quota, for display. The quota is not enforced yet; the only enforced limit is a safety ceiling (`403 storage_quota_exceeded` on create).",
        "operationId": "getFileUsage",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "Usage",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileUsage"
                },
                "example": {
                  "usedBytes": 48211033,
                  "quotaBytes": 1073741824
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/files/{fileId}": {
      "get": {
        "summary": "Get a file",
        "description": "Returns a file in any status. A missing, expired or other organization's file is 404.",
        "operationId": "getFile",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/fileId"
          },
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "The file",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/File"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "file_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "summary": "Update a file",
        "description": "Changes a file's expiry or display name. The bytes are immutable.",
        "operationId": "updateFile",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/fileId"
          },
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateFileRequest"
              },
              "examples": {
                "extend": {
                  "summary": "Keep for a week from now",
                  "value": {
                    "expiresIn": 604800
                  }
                },
                "rename": {
                  "summary": "Rename",
                  "value": {
                    "name": "RC 4.2.0"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated file",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/File"
                }
              }
            }
          },
          "400": {
            "description": "invalid_body, invalid_name",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "readwrite_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "file_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "expiry_out_of_range",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "summary": "Delete a file",
        "description": "Deletes the file immediately: it no longer appears in lists, cannot be fetched or downloaded, and stops counting as usage. Download URLs already issued keep working until they expire.",
        "operationId": "deleteFile",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/fileId"
          },
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "readwrite_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "file_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/files/{fileId}/complete": {
      "post": {
        "summary": "Complete a file upload",
        "description": "Call after the PUT. Verifies the stored bytes against the declared size and SHA-256 and returns the file as `ready` or `failed`. Synchronous; there is nothing to poll. Idempotent: a `ready` or `failed` file is returned unchanged.",
        "operationId": "completeFile",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/fileId"
          },
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "The file, `ready` or `failed`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/File"
                },
                "examples": {
                  "ready": {
                    "summary": "Verified",
                    "value": {
                      "id": "2f1c8e4a-1f2b-4a1e-9a2c-0b7f5d3e6a11",
                      "app": null,
                      "platform": null,
                      "filename": "app-release.apk",
                      "filesize": 48211033,
                      "sha256": "9f2c0b7f5d3e6a1100000000000000000000000000000000000000000000e1a4",
                      "name": "app-release.apk",
                      "bundleId": null,
                      "version": null,
                      "buildNumber": null,
                      "minOsVersion": null,
                      "isSimulator": false,
                      "build": {
                        "branch": "fix/checkout",
                        "commit": "3dbd301"
                      },
                      "source": "api",
                      "organization": "8b0e2c1a-5d3f-4e7b-9a61-2c4d8f0a1b33",
                      "user": "github|1234",
                      "failureReason": null,
                      "createdAt": "2026-09-18T10:00:00Z",
                      "lastUsedAt": null,
                      "expiresAt": "2026-10-18T10:00:00Z",
                      "status": "ready"
                    }
                  },
                  "failed": {
                    "summary": "Stored bytes did not match",
                    "value": {
                      "id": "2f1c8e4a-1f2b-4a1e-9a2c-0b7f5d3e6a11",
                      "app": null,
                      "platform": null,
                      "filename": "app-release.apk",
                      "filesize": 48211033,
                      "sha256": "9f2c0b7f5d3e6a1100000000000000000000000000000000000000000000e1a4",
                      "name": "app-release.apk",
                      "bundleId": null,
                      "version": null,
                      "buildNumber": null,
                      "minOsVersion": null,
                      "isSimulator": false,
                      "build": {
                        "branch": "fix/checkout",
                        "commit": "3dbd301"
                      },
                      "source": "api",
                      "organization": "8b0e2c1a-5d3f-4e7b-9a61-2c4d8f0a1b33",
                      "user": "github|1234",
                      "failureReason": "checksum_mismatch",
                      "createdAt": "2026-09-18T10:00:00Z",
                      "lastUsedAt": null,
                      "expiresAt": "2026-10-18T10:00:00Z",
                      "status": "failed"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "readwrite_required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "file_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "upload_missing: the bytes have not been uploaded yet; the file stays pendingUpload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/files/{fileId}/download": {
      "get": {
        "summary": "Download a file",
        "description": "Returns a presigned download URL valid for 5 minutes; it saves under the file's original filename. Any role. Does not change `lastUsedAt`. Returns JSON rather than redirecting, so the route itself cannot be shared as a download link.",
        "operationId": "downloadFile",
        "tags": [
          "Files"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/fileId"
          },
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "A short-lived download URL",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileDownload"
                },
                "example": {
                  "url": "https://mobilenexthq-app-files.s3.us-west-2.amazonaws.com/files/2f1c8e4a-1f2b-4a1e-9a2c-0b7f5d3e6a11?X-Amz-Signature=abc123",
                  "expiresAt": "2026-09-18T10:05:00Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "file_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "file_not_ready: the file is not ready yet, or failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/apps": {
      "get": {
        "summary": "List apps",
        "description": "Apps with at least one ready, unexpired file, newest first. Any role.",
        "operationId": "listApps",
        "tags": [
          "Apps"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/platformQuery"
          },
          {
            "$ref": "#/components/parameters/q"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of apps",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AppList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/apps/{appId}": {
      "get": {
        "summary": "Get an app",
        "description": "Any role. An app whose files have all expired is still returned, with `fileCount: 0` and `latestFile: null`.",
        "operationId": "getApp",
        "tags": [
          "Apps"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/appId"
          },
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "The app",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/App"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "app_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/apps/{appId}/files": {
      "get": {
        "summary": "List an app's files",
        "description": "Same as listFiles, restricted to this app. For a branch's newest build: `?branch=main&limit=1`.",
        "operationId": "listAppFiles",
        "tags": [
          "Apps"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/appId"
          },
          {
            "$ref": "#/components/parameters/sha256Query"
          },
          {
            "$ref": "#/components/parameters/platformQuery"
          },
          {
            "$ref": "#/components/parameters/bundleIdQuery"
          },
          {
            "$ref": "#/components/parameters/branchQuery"
          },
          {
            "$ref": "#/components/parameters/commitQuery"
          },
          {
            "$ref": "#/components/parameters/fileStatusQuery"
          },
          {
            "$ref": "#/components/parameters/q"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/cursor"
          },
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of the app's files",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FileList"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "app_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/apps/{appId}/icon": {
      "get": {
        "summary": "Get an app's icon",
        "description": "The icon as PNG (at most 512×512). Supports `If-None-Match` with the returned `ETag`. Any role.",
        "operationId": "getAppIcon",
        "tags": [
          "Apps"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/appId"
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "responses": {
          "200": {
            "description": "PNG icon",
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Not modified"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "app_not_found, or icon_not_found when the app has no icon",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/sessions/{sessionId}/devices/{deviceId}/install": {
      "post": {
        "summary": "Install a stored file on a device",
        "description": "Installs a ready file from app storage on a device of this session, without uploading it again. Synchronous: a large app can take minutes.",
        "operationId": "installFile",
        "tags": [
          "Devices",
          "Files"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/sessionId"
          },
          {
            "$ref": "#/components/parameters/deviceId"
          },
          {
            "$ref": "#/components/parameters/organizationIdHeader"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InstallFileRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Installed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InstallFileResult"
                }
              }
            }
          },
          "400": {
            "description": "invalid_request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "readwrite_required, or blocked_app",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "session or device_not_found, or file_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "file_not_ready",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "platform_mismatch, or install_requires_new_device: an iOS app can only be installed when a device is started (allocate with `file`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "install_failed: the device could not install the app",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key starting with mob_"
      }
    },
    "parameters": {
      "organizationIdHeader": {
        "name": "X-Organization-Id",
        "in": "header",
        "required": false,
        "description": "Selects which of the caller's organizations the request acts on. When omitted, the caller's default organization is used.",
        "schema": {
          "type": "string"
        }
      },
      "organizationPathId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Organization id",
        "schema": {
          "type": "string"
        }
      },
      "testResultId": {
        "name": "testResultId",
        "in": "path",
        "required": true,
        "description": "Test result UUID",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "assetId": {
        "name": "assetId",
        "in": "path",
        "required": true,
        "description": "Asset UUID",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "invitationId": {
        "name": "invitationId",
        "in": "path",
        "required": true,
        "description": "Invitation UUID",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "sessionId": {
        "name": "sessionId",
        "in": "path",
        "required": true,
        "description": "Session UUID",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "instanceId": {
        "name": "instanceId",
        "in": "path",
        "required": true,
        "description": "Instance UUID",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "deviceId": {
        "name": "deviceId",
        "in": "path",
        "required": true,
        "description": "The physical device's own id, as returned in SessionDevice.info.serial — a UDID on iOS, a serial on Android. Not the allocation id (SessionDevice.id).",
        "schema": {
          "type": "string"
        },
        "example": "00008150-001C30842678401C"
      },
      "chatId": {
        "name": "chatId",
        "in": "path",
        "required": true,
        "description": "Chat id, e.g. chat_2f1c…. Resolved within its session; a chat id from another session is not found.",
        "schema": {
          "type": "string"
        }
      },
      "messageId": {
        "name": "messageId",
        "in": "path",
        "required": true,
        "description": "Message id, e.g. msg_9a3b…",
        "schema": {
          "type": "string"
        }
      },
      "memberUserId": {
        "name": "userId",
        "in": "path",
        "required": true,
        "description": "The member's `user` id as returned by the members list (URL-encoded), or the literal `me` for the caller.",
        "schema": {
          "type": "string"
        }
      },
      "fileId": {
        "name": "fileId",
        "in": "path",
        "required": true,
        "description": "File UUID",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Page size, 1 to 100. Default 25.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 25
        }
      },
      "cursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "`nextCursor` from the previous page. Opaque.",
        "schema": {
          "type": "string"
        }
      },
      "sha256Query": {
        "name": "sha256",
        "in": "query",
        "required": false,
        "description": "Only the file with this lowercase hex SHA-256.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9a-f]{64}$"
        }
      },
      "appQuery": {
        "name": "app",
        "in": "query",
        "required": false,
        "description": "Only files of this app.",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "platformQuery": {
        "name": "platform",
        "in": "query",
        "required": false,
        "description": "Only files for this platform.",
        "schema": {
          "type": "string",
          "enum": [
            "android",
            "ios"
          ]
        }
      },
      "bundleIdQuery": {
        "name": "bundleId",
        "in": "query",
        "required": false,
        "description": "Only files with this bundle id.",
        "schema": {
          "type": "string"
        }
      },
      "branchQuery": {
        "name": "branch",
        "in": "query",
        "required": false,
        "description": "Only files whose build.branch equals this.",
        "schema": {
          "type": "string"
        }
      },
      "commitQuery": {
        "name": "commit",
        "in": "query",
        "required": false,
        "description": "Only files whose build.commit equals this.",
        "schema": {
          "type": "string"
        }
      },
      "fileStatusQuery": {
        "name": "status",
        "in": "query",
        "required": false,
        "description": "Only files in this status. Without it, ready and processing files are listed; pendingUpload and failed are for debugging.",
        "schema": {
          "type": "string",
          "enum": [
            "pendingUpload",
            "processing",
            "ready",
            "failed"
          ]
        }
      },
      "q": {
        "name": "q",
        "in": "query",
        "required": false,
        "description": "Case-insensitive substring of the name, filename or bundle id.",
        "schema": {
          "type": "string"
        }
      },
      "appId": {
        "name": "appId",
        "in": "path",
        "required": true,
        "description": "App UUID",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid Bearer token",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BadRequest": {
        "description": "Invalid request body",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Caller lacks permission for this action (e.g. not an admin)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "Request conflicts with current state (e.g. already a member or already invited)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "InternalServerError": {
        "description": "Unexpected server error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Me": {
        "type": "object",
        "required": [
          "id",
          "email"
        ],
        "description": "The authenticated user. To learn which organizations the caller belongs to (and their role in each), call GET /api/v1/organizations.",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The user's own id"
          },
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          }
        }
      },
      "MemberRole": {
        "type": "string",
        "enum": [
          "admin",
          "readwrite",
          "readonly"
        ],
        "description": "Organization role. admin: manage members and billing, plus everything readwrite can do. readwrite: start sessions, create API keys, delete sessions. readonly: view sessions, API keys, and test results only."
      },
      "OrganizationMember": {
        "type": "object",
        "required": [
          "user",
          "email",
          "role",
          "joinedAt"
        ],
        "properties": {
          "user": {
            "type": "string",
            "format": "uuid",
            "description": "Reference to the user"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": "string",
            "nullable": true
          },
          "role": {
            "$ref": "#/components/schemas/MemberRole"
          },
          "joinedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MemberInvitation": {
        "type": "object",
        "required": [
          "id",
          "email",
          "role",
          "status",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "role": {
            "$ref": "#/components/schemas/MemberRole"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "accepted",
              "revoked",
              "expired"
            ]
          },
          "inviter": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "Reference to the inviting user"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "MemberList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrganizationMember"
            }
          }
        }
      },
      "MemberInvitationList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "description": "Pending invitations only",
            "items": {
              "$ref": "#/components/schemas/MemberInvitation"
            }
          }
        }
      },
      "InviteMemberRequest": {
        "type": "object",
        "required": [
          "email",
          "role"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "role": {
            "$ref": "#/components/schemas/MemberRole"
          }
        }
      },
      "Organization": {
        "type": "object",
        "required": [
          "id",
          "type",
          "role",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The organization's own id"
          },
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Organization name; null for the Personal organization"
          },
          "type": {
            "type": "string",
            "enum": [
              "hobby",
              "starter",
              "team",
              "enterprise"
            ]
          },
          "role": {
            "$ref": "#/components/schemas/MemberRole"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "OrganizationList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Organization"
            }
          }
        }
      },
      "CreateOrganizationRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Organization name; required, not \"Personal\", unique among the caller's organizations"
          }
        }
      },
      "IncomingInvitation": {
        "type": "object",
        "required": [
          "id",
          "organization",
          "role",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "organization": {
            "type": "object",
            "description": "Reference to the inviting organization",
            "required": [
              "id"
            ],
            "properties": {
              "id": {
                "type": "string",
                "description": "Inviting organization id"
              },
              "name": {
                "type": "string",
                "nullable": true,
                "description": "Inviting organization name"
              }
            }
          },
          "role": {
            "$ref": "#/components/schemas/MemberRole"
          },
          "inviterEmail": {
            "type": "string",
            "nullable": true,
            "description": "Email of the inviter"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "nullable": true,
            "format": "date-time"
          }
        }
      },
      "IncomingInvitationList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/IncomingInvitation"
            }
          }
        }
      },
      "Balance": {
        "type": "object",
        "required": [
          "accountType",
          "overageCapUsd",
          "subscription",
          "resources"
        ],
        "properties": {
          "accountType": {
            "$ref": "#/components/schemas/AccountType"
          },
          "overageCapUsd": {
            "type": "number",
            "description": "Dollars of overage allowed past the plan's included seconds in one period, at $0.15 per minute. 0 means the organization hard-stops when it runs out.",
            "example": 25
          },
          "subscription": {
            "description": "The Stripe subscription backing a paid plan, or null on the free hobby plan.",
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/BillingSubscription"
              }
            ]
          },
          "resources": {
            "type": "array",
            "description": "Remaining usage per metered resource. Today only mobile is metered; more resources are added as array entries, so read the entry whose resource you want rather than the first one.",
            "items": {
              "$ref": "#/components/schemas/ResourceBalance"
            }
          }
        }
      },
      "ApiKey": {
        "type": "object",
        "description": "An API key the caller owns. The secret is never included; only apiKeySuffix is shown.",
        "required": [
          "id",
          "apiKeySuffix",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "apiKeySuffix": {
            "type": "string",
            "description": "Last few characters of the secret, to help identify the key",
            "example": "a1b"
          },
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Human-readable label for the key"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastUsedAt": {
            "type": "string",
            "nullable": true,
            "format": "date-time",
            "description": "When the key was last used; null if never used"
          }
        }
      },
      "ApiKeyList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiKey"
            }
          }
        }
      },
      "CreateApiKeyRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Human-readable label for the key",
            "example": "CI pipeline"
          }
        }
      },
      "CreatedApiKey": {
        "type": "object",
        "description": "A newly created API key. apiKey is the full secret and is returned only here, only once.",
        "required": [
          "id",
          "apiKey",
          "name",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "apiKey": {
            "type": "string",
            "description": "The full secret, starting with mob_. Shown once; store it now.",
            "example": "mob_a1b2c3..."
          },
          "name": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SessionDevice": {
        "type": "object",
        "required": [
          "id",
          "status",
          "info",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Allocation id. Stable and always present, including while the device is still provisioning and no physical device exists yet. This is not the device's own id — see info.serial."
          },
          "status": {
            "type": "string",
            "enum": [
              "provisioning",
              "in_use",
              "released"
            ],
            "description": "Device lifecycle status. `provisioning` means a device is being booted and has no info.serial yet; `in_use` means it is allocated and driveable; `released` means it has been returned to the pool. More values will be added."
          },
          "info": {
            "type": "object",
            "description": "Descriptive attributes of the device.",
            "required": [
              "platform",
              "name",
              "osVersion"
            ],
            "properties": {
              "platform": {
                "type": "string",
                "example": "ios"
              },
              "type": {
                "type": "string",
                "example": "real"
              },
              "name": {
                "type": "string"
              },
              "osVersion": {
                "type": "string"
              },
              "serial": {
                "type": "string",
                "description": "The physical device's own id — a UDID on iOS, a serial on Android. Absent while the device is provisioning. This is the id accepted by the device endpoints and tools.",
                "example": "00008150-001C30842678401C"
              }
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the device was added to the session"
          },
          "releasedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the device was released (billing end)"
          }
        }
      },
      "DeviceFilter": {
        "type": "object",
        "description": "A single device-selection criterion. Multiple filters are ANDed together. This is the same filter DSL used by the fleet.allocate RPC.",
        "required": [
          "attribute",
          "operator",
          "value"
        ],
        "properties": {
          "attribute": {
            "type": "string",
            "enum": [
              "platform",
              "type",
              "name",
              "version"
            ]
          },
          "operator": {
            "type": "string",
            "enum": [
              "EQUALS",
              "GREATER_THAN",
              "GREATER_THAN_OR_EQUALS",
              "LESS_THAN",
              "LESS_THAN_OR_EQUALS",
              "STARTS_WITH",
              "CONTAINS"
            ],
            "description": "Allowed operators depend on the attribute: platform and type accept only EQUALS; name accepts EQUALS, STARTS_WITH, CONTAINS; version accepts EQUALS and the comparison operators."
          },
          "value": {
            "type": "string",
            "description": "Must be non-empty. For platform: 'ios' or 'android'. For type: 'real'.",
            "example": "ios"
          }
        }
      },
      "AllocateDeviceRequest": {
        "type": "object",
        "description": "Device selection for allocation, using the same filter DSL as the fleet.allocate RPC. Rules: at least one filter; exactly one platform filter with the EQUALS operator ('ios' or 'android'); a type filter, if present, must equal 'real'.",
        "required": [
          "filters"
        ],
        "properties": {
          "filters": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DeviceFilter"
            },
            "minItems": 1
          },
          "file": {
            "type": "string",
            "format": "uuid",
            "description": "A ready file from app storage to install on the device. Its platform must match the platform filter. On iOS the app is installed while the device is provisioned, so a new device is always started."
          },
          "launchAfterInstall": {
            "type": "boolean",
            "default": false,
            "description": "Launch the app from `file` once it is installed and the device is ready. A failed launch does not fail the allocation."
          }
        }
      },
      "DeviceList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SessionDevice"
            }
          },
          "nextCursor": {
            "type": "string",
            "nullable": true,
            "description": "Cursor for the next page; absent when there are no more results"
          }
        }
      },
      "Session": {
        "type": "object",
        "required": [
          "id",
          "user",
          "devices",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The session's own id"
          },
          "title": {
            "type": "string",
            "nullable": true,
            "description": "Human-readable title; may be null"
          },
          "user": {
            "type": "string",
            "description": "User who created this session"
          },
          "userName": {
            "type": "string",
            "nullable": true,
            "description": "Display name of the user who created this session. Only on the organization listing."
          },
          "userEmail": {
            "type": "string",
            "description": "Email of the user who created this session. Only on the organization listing."
          },
          "devices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SessionDevice"
            },
            "description": "Device allocations linked to this session, ordered by createdAt ascending"
          },
          "instances": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Instance"
            },
            "description": "Instances linked to this session, ordered by createdAt ascending"
          },
          "chats": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Chat id"
                }
              }
            },
            "description": "Chats in this session, oldest first. A client restoring a session's conversation reads the last one."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Chat": {
        "type": "object",
        "description": "A conversation with the assistant, inside a session.",
        "required": [
          "id",
          "session",
          "organization",
          "user",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The chat's own id",
            "example": "chat_2f1c8e4a-1f2b-4a1e-9a2c-0b7f5d3e6a11"
          },
          "session": {
            "type": "string",
            "format": "uuid",
            "description": "Reference to the session the chat belongs to"
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "organization": {
            "type": "string",
            "description": "Reference to the owning organization"
          },
          "user": {
            "type": "string",
            "description": "Reference to the user who created the chat"
          },
          "metadata": {
            "type": "object",
            "description": "Client-supplied metadata. deviceId binds the chat to an allocated device.",
            "properties": {
              "deviceId": {
                "type": "string",
                "format": "uuid"
              }
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ChatList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Chat"
            }
          }
        }
      },
      "CreateChatRequest": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Human-readable title"
          },
          "metadata": {
            "type": "object",
            "description": "Client-supplied metadata. Set deviceId to bind the chat to a device allocated in this session.",
            "properties": {
              "deviceId": {
                "type": "string",
                "format": "uuid"
              }
            }
          }
        }
      },
      "MessageContentPart": {
        "type": "object",
        "required": [
          "type",
          "text"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "text"
            ]
          },
          "text": {
            "type": "string"
          }
        }
      },
      "Execution": {
        "type": "object",
        "description": "The work behind an assistant message. Synthesized from the message; not a separate resource.",
        "required": [
          "id",
          "status",
          "triggerMessage",
          "responseMessage",
          "eventsUrl",
          "toolCallsUrl",
          "cancelUrl"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "exec_7d2a…"
          },
          "status": {
            "type": "string",
            "enum": [
              "running",
              "completed",
              "failed",
              "cancelled"
            ]
          },
          "triggerMessage": {
            "type": "string",
            "description": "Reference to the user message that started this execution"
          },
          "responseMessage": {
            "type": "string",
            "description": "Reference to the assistant message this execution produces"
          },
          "eventsUrl": {
            "type": "string",
            "description": "SSE stream of this execution's events"
          },
          "toolCallsUrl": {
            "type": "string"
          },
          "cancelUrl": {
            "type": "string"
          },
          "startedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "completedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "ToolCall": {
        "type": "object",
        "description": "One tool the assistant invoked while producing a message.",
        "required": [
          "id",
          "execution",
          "message",
          "tool",
          "status",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "toolcall_1b9e…"
          },
          "execution": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "tool": {
            "type": "string",
            "example": "instance_bash"
          },
          "status": {
            "type": "string",
            "enum": [
              "running",
              "completed",
              "failed",
              "rejected"
            ]
          },
          "input": {
            "type": "object",
            "description": "Tool arguments, shape depends on the tool"
          },
          "output": {
            "type": "object",
            "description": "Tool result, shape depends on the tool"
          },
          "error": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "ToolCallList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ToolCall"
            }
          }
        }
      },
      "Message": {
        "type": "object",
        "required": [
          "id",
          "chat",
          "role",
          "status",
          "content",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "msg_9a3b…"
          },
          "chat": {
            "type": "string",
            "description": "Reference to the chat"
          },
          "role": {
            "type": "string",
            "enum": [
              "user",
              "assistant",
              "system"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "running",
              "completed",
              "failed",
              "cancelled"
            ],
            "description": "For an assistant message this is its execution's status."
          },
          "content": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MessageContentPart"
            }
          },
          "execution": {
            "$ref": "#/components/schemas/Execution"
          },
          "toolCalls": {
            "type": "array",
            "description": "Tool calls made while producing this message. Absent when there were none.",
            "items": {
              "$ref": "#/components/schemas/ToolCall"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "MessageList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            }
          },
          "nextCursor": {
            "type": "string",
            "nullable": true,
            "description": "Cursor for the next page; absent when there are no more results"
          }
        }
      },
      "CreateMessageRequest": {
        "type": "object",
        "required": [
          "role",
          "content"
        ],
        "properties": {
          "role": {
            "type": "string",
            "enum": [
              "user"
            ]
          },
          "content": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/MessageContentPart"
            }
          },
          "execute": {
            "type": "boolean",
            "default": true,
            "description": "false stores the user message without running the assistant"
          },
          "mode": {
            "type": "string",
            "enum": [
              "async",
              "sync"
            ],
            "default": "async",
            "description": "sync runs the assistant inline and returns the completed message"
          },
          "stream": {
            "type": "boolean",
            "default": false,
            "description": "true responds with a text/event-stream instead of JSON"
          },
          "model": {
            "type": "string",
            "description": "Overrides the assistant's default model for this turn"
          },
          "metadata": {
            "type": "object"
          }
        }
      },
      "Machine": {
        "type": "object",
        "description": "The compute backing an instance.",
        "required": [
          "arch",
          "cpuCores",
          "memoryMb",
          "diskMb"
        ],
        "properties": {
          "arch": {
            "type": "string",
            "enum": [
              "arm64"
            ],
            "description": "CPU architecture. Only arm64 is supported today."
          },
          "cpuCores": {
            "type": "integer",
            "example": 8
          },
          "memoryMb": {
            "type": "integer",
            "example": 16384
          },
          "diskMb": {
            "type": "integer",
            "example": 262144
          }
        }
      },
      "Instance": {
        "type": "object",
        "description": "A build/automation machine allocated to a session.",
        "required": [
          "id",
          "status",
          "os",
          "image",
          "machine",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The instance's own id"
          },
          "status": {
            "type": "string",
            "enum": [
              "provisioning",
              "ready",
              "released",
              "failed"
            ],
            "description": "Lifecycle: provisioning -> ready, then released, or failed if provisioning could not complete"
          },
          "os": {
            "type": "string",
            "enum": [
              "macos"
            ],
            "description": "Instance operating system. Only macos is supported today."
          },
          "image": {
            "type": "string",
            "description": "Server-assigned base image the instance booted from",
            "example": "macos-15-xcode-16"
          },
          "machine": {
            "$ref": "#/components/schemas/Machine"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the instance was allocated (billing start)"
          },
          "readyAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the instance finished provisioning; null while provisioning"
          },
          "releasedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the instance was released (billing end); null while active"
          }
        }
      },
      "InstanceList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Instance"
            }
          },
          "nextCursor": {
            "type": "string",
            "nullable": true,
            "description": "Cursor for the next page; absent when there are no more results"
          }
        }
      },
      "CreateInstanceRequest": {
        "type": "object",
        "required": [
          "os"
        ],
        "properties": {
          "os": {
            "type": "string",
            "enum": [
              "macos"
            ],
            "description": "Instance operating system. Only macos is supported today."
          }
        }
      },
      "SessionList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Session"
            }
          },
          "nextCursor": {
            "type": "string",
            "nullable": true,
            "description": "Cursor for the next page; absent when there are no more results"
          }
        }
      },
      "CreateTestResultRequest": {
        "type": "object",
        "required": [
          "name",
          "userAgent"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Human-readable name for this test run",
            "example": "Smoke suite"
          },
          "userAgent": {
            "type": "string",
            "description": "Reporter name and version",
            "example": "mobilewright/0.0.39"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "passed",
              "failed",
              "flaky",
              "errored"
            ],
            "description": "Run status. queued and running mark a run still in progress; errored means the run itself broke (infrastructure, crash) rather than a test; the rest are terminal test outcomes. Omit on create to derive it from stats: unexpected > 0 is failed, flaky > 0 is flaky, otherwise passed (also when stats are absent)."
          }
        }
      },
      "UpdateTestResultRequest": {
        "type": "object",
        "description": "Partial update: absent fields are left untouched. Sending stats without status re-derives the status from the stats.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "passed",
              "failed",
              "flaky",
              "errored"
            ],
            "description": "Run status. queued and running mark a run still in progress; errored means the run itself broke (infrastructure, crash) rather than a test; the rest are terminal test outcomes. Omit on create to derive it from stats: unexpected > 0 is failed, flaky > 0 is flaky, otherwise passed (also when stats are absent)."
          },
          "environment": {
            "type": "string",
            "example": "staging"
          },
          "stats": {
            "type": "object",
            "description": "Aggregate Playwright run stats",
            "properties": {
              "startTime": {
                "type": "string",
                "format": "date-time"
              },
              "duration": {
                "type": "number",
                "description": "Total duration in milliseconds"
              },
              "expected": {
                "type": "integer"
              },
              "unexpected": {
                "type": "integer"
              },
              "skipped": {
                "type": "integer"
              },
              "flaky": {
                "type": "integer"
              }
            }
          }
        }
      },
      "TestResult": {
        "type": "object",
        "required": [
          "id",
          "name",
          "userAgent",
          "createdAt",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "Smoke suite"
          },
          "userAgent": {
            "type": "string",
            "example": "mobilewright/0.0.39"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "passed",
              "failed",
              "flaky",
              "errored"
            ],
            "description": "Run status. queued and running mark a run still in progress; errored means the run itself broke (infrastructure, crash) rather than a test; the rest are terminal test outcomes. Omit on create to derive it from stats: unexpected > 0 is failed, flaky > 0 is flaky, otherwise passed (also when stats are absent)."
          }
        }
      },
      "TestResultList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TestResult"
            }
          }
        }
      },
      "Asset": {
        "type": "object",
        "required": [
          "id",
          "name",
          "contentType",
          "size",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "trace.zip"
          },
          "contentType": {
            "type": "string",
            "example": "application/zip"
          },
          "size": {
            "type": "integer",
            "description": "File size in bytes"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AssetList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Asset"
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable, machine-readable error code (e.g. invitation_not_found, admin_required)"
              },
              "message": {
                "type": "string",
                "description": "Human-readable error description"
              }
            }
          }
        }
      },
      "AccountType": {
        "type": "string",
        "description": "The organization's plan.",
        "enum": [
          "hobby",
          "starter",
          "team",
          "enterprise"
        ],
        "example": "starter"
      },
      "BillingSubscription": {
        "type": "object",
        "required": [
          "status",
          "currentPeriodEnd",
          "cancelAtPeriodEnd"
        ],
        "properties": {
          "status": {
            "type": "string",
            "description": "Stripe subscription status. active, trialing, and past_due all keep the plan's entitlements; the plan ends only when the subscription is deleted.",
            "example": "active"
          },
          "currentPeriodEnd": {
            "type": "string",
            "format": "date-time",
            "description": "When the current billing period ends, and the included seconds are granted again."
          },
          "cancelAtPeriodEnd": {
            "type": "boolean",
            "description": "True when the subscription is set to end at currentPeriodEnd rather than renew."
          }
        }
      },
      "ResourceBalance": {
        "type": "object",
        "required": [
          "resource",
          "secondsRemaining",
          "periodStart",
          "periodEnd"
        ],
        "properties": {
          "resource": {
            "$ref": "#/components/schemas/MeteredResource"
          },
          "secondsRemaining": {
            "type": "integer",
            "description": "Seconds left in the current period. Negative on a paid plan means overage already used, billed on the next invoice up to overageCapUsd.",
            "example": 11400
          },
          "periodStart": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Start of the period this balance belongs to, or null when the organization has never been granted any."
          },
          "periodEnd": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "End of the period this balance belongs to, or null when the organization has never been granted any."
          }
        }
      },
      "MeteredResource": {
        "type": "string",
        "description": "A metered resource. Only \"mobile\" exists today; more are added over time, so treat an unrecognised value as a resource you do not handle rather than an error.",
        "example": "mobile"
      },
      "UsageTransaction": {
        "type": "object",
        "required": [
          "transactionId",
          "resource",
          "type",
          "seconds",
          "balanceAfter",
          "createdAt"
        ],
        "properties": {
          "transactionId": {
            "type": "string",
            "format": "uuid",
            "description": "Ledger entry id"
          },
          "accountId": {
            "type": "string",
            "description": "Organization the entry belongs to"
          },
          "resource": {
            "$ref": "#/components/schemas/MeteredResource"
          },
          "type": {
            "type": "string",
            "description": "Why the balance changed: one of plan-grant, daily-grant, trial-grant, device-usage, coupon, adjustment, conversion. New types are added over time, so treat an unrecognised value as an opaque label rather than an error.",
            "example": "device-usage"
          },
          "seconds": {
            "type": "integer",
            "description": "Signed change in seconds: positive for a grant, negative for usage.",
            "example": -300
          },
          "balanceAfter": {
            "type": "integer",
            "description": "Seconds remaining immediately after this entry"
          },
          "description": {
            "type": "string",
            "description": "Human-readable explanation of the entry"
          },
          "allocationId": {
            "type": "string",
            "description": "The device allocation this entry was charged for, when it was device usage"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "UsageTransactionList": {
        "type": "object",
        "required": [
          "transactions"
        ],
        "properties": {
          "transactions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UsageTransaction"
            }
          }
        }
      },
      "CreateBillingCheckoutSessionRequest": {
        "type": "object",
        "required": [
          "plan"
        ],
        "properties": {
          "plan": {
            "type": "string",
            "description": "The plan to subscribe to.",
            "enum": [
              "starter",
              "team"
            ],
            "example": "starter"
          }
        }
      },
      "BillingRedirect": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Stripe-hosted page to send the browser to. Single-use and short-lived: request a new one rather than storing it.",
            "example": "https://billing.stripe.com/session/live_YWNjdF8x"
          }
        }
      },
      "SetBillingOverageCapRequest": {
        "type": "object",
        "required": [
          "capUsd"
        ],
        "properties": {
          "capUsd": {
            "type": "number",
            "description": "Dollars of overage allowed in one period, between 0 and 1000. 0 hard-stops at the plan's included seconds.",
            "minimum": 0,
            "maximum": 1000,
            "example": 25
          }
        }
      },
      "RenameOrganizationRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 255,
            "description": "New name. Same rules as create: not blank, not \"Personal\", not the name of another organization the caller belongs to."
          }
        }
      },
      "ChangeMemberRoleRequest": {
        "type": "object",
        "required": [
          "role"
        ],
        "properties": {
          "role": {
            "$ref": "#/components/schemas/MemberRole"
          }
        }
      },
      "FileBuild": {
        "type": "object",
        "description": "Where the build came from. Every field is optional; CI clients fill it from their environment.",
        "properties": {
          "branch": {
            "type": "string",
            "maxLength": 255,
            "example": "fix/checkout"
          },
          "commit": {
            "type": "string",
            "maxLength": 64,
            "example": "3dbd301"
          },
          "ciRunUrl": {
            "type": "string",
            "format": "uri",
            "maxLength": 1024,
            "description": "https only. Stored and displayed, never fetched.",
            "example": "https://github.com/acme/shop/actions/runs/991"
          }
        }
      },
      "FileUpload": {
        "type": "object",
        "description": "Present only while the caller is expected to upload the bytes. Send exactly these headers; the URL is signed for the declared size and SHA-256, so any other bytes are rejected by storage.",
        "required": [
          "method",
          "url",
          "headers",
          "expiresAt"
        ],
        "properties": {
          "method": {
            "type": "string",
            "enum": [
              "PUT"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "example": {
              "X-Amz-Checksum-Sha256": "WJG1tSLV3whtD/CxEPvZ0hu0/HFjrzTQgoai6Eb2vgM=",
              "Content-Length": "48211033"
            }
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the URL stops working. Calling create again returns a fresh one."
          }
        }
      },
      "File": {
        "type": "object",
        "description": "One stored app binary. Immutable bytes, unique per organization by SHA-256.",
        "required": [
          "id",
          "status",
          "app",
          "platform",
          "filename",
          "filesize",
          "sha256",
          "name",
          "bundleId",
          "version",
          "buildNumber",
          "minOsVersion",
          "isSimulator",
          "build",
          "source",
          "organization",
          "user",
          "failureReason",
          "createdAt",
          "lastUsedAt",
          "expiresAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "pendingUpload",
              "processing",
              "ready",
              "failed"
            ],
            "description": "pendingUpload: bytes not verified yet. processing: verification running (seen only by a concurrent caller). ready: installable. failed: see failureReason; creating the same sha256 again resets it."
          },
          "app": {
            "type": "string",
            "format": "uuid",
            "nullable": true,
            "description": "The app this file belongs to, once metadata is extracted."
          },
          "platform": {
            "type": "string",
            "enum": [
              "android",
              "ios",
              null
            ],
            "nullable": true,
            "description": "Known once metadata is extracted."
          },
          "filename": {
            "type": "string",
            "example": "app-release.apk"
          },
          "filesize": {
            "type": "integer",
            "format": "int64",
            "example": 48211033
          },
          "sha256": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$"
          },
          "name": {
            "type": "string",
            "description": "Display name. Defaults to the filename until metadata is extracted.",
            "example": "Checkout fix"
          },
          "bundleId": {
            "type": "string",
            "nullable": true,
            "description": "Android package name or iOS bundle identifier.",
            "example": "com.acme.shop"
          },
          "version": {
            "type": "string",
            "nullable": true,
            "description": "Version name (Android) or CFBundleShortVersionString (iOS).",
            "example": "4.2.0"
          },
          "buildNumber": {
            "type": "string",
            "nullable": true,
            "description": "Version code (Android) or CFBundleVersion (iOS).",
            "example": "4201"
          },
          "minOsVersion": {
            "type": "string",
            "nullable": true,
            "description": "Minimum OS version the build supports.",
            "example": "8.0"
          },
          "isSimulator": {
            "type": "boolean",
            "description": "True for an iOS simulator build.",
            "example": false
          },
          "build": {
            "type": "object",
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/FileBuild"
              }
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "api",
              "web",
              "mcp",
              "chat"
            ],
            "description": "How the file was created, derived from the credential."
          },
          "organization": {
            "type": "string",
            "description": "Organization that owns the file."
          },
          "user": {
            "type": "string",
            "nullable": true,
            "description": "Uploader; null if the user was deleted."
          },
          "failureReason": {
            "type": "string",
            "nullable": true,
            "enum": [
              "checksum_mismatch",
              "size_mismatch",
              "unsupported_format",
              "blocked_app",
              "extraction_failed",
              "upload_missing",
              null
            ],
            "description": "Why the file failed; null otherwise."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastUsedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Last dedup hit or install."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "The file and its bytes are deleted after this."
          }
        }
      },
      "CreateFileResult": {
        "allOf": [
          {
            "$ref": "#/components/schemas/File"
          },
          {
            "type": "object",
            "properties": {
              "upload": {
                "$ref": "#/components/schemas/FileUpload"
              }
            }
          }
        ]
      },
      "CreateFileRequest": {
        "type": "object",
        "required": [
          "filename",
          "filesize",
          "sha256"
        ],
        "properties": {
          "filename": {
            "type": "string",
            "pattern": "^[0-9a-zA-Z_.-]+$",
            "description": "Must end in .apk or .ipa.",
            "example": "app-release.apk"
          },
          "filesize": {
            "type": "integer",
            "format": "int64",
            "minimum": 1,
            "description": "Bytes. At most the plan's per-file cap: hobby 250 MB, starter 1 GB, team and enterprise 2 GB.",
            "example": 48211033
          },
          "sha256": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$",
            "description": "Lowercase hex SHA-256 of the file."
          },
          "expiresIn": {
            "type": "integer",
            "format": "int64",
            "minimum": 600,
            "maximum": 7776000,
            "default": 2592000,
            "description": "Seconds until the file expires: 10 minutes to 90 days, default 30 days. A dedup hit only ever extends the expiry."
          },
          "name": {
            "type": "string",
            "maxLength": 255,
            "description": "Display name. Kept from the first create on a dedup hit."
          },
          "build": {
            "$ref": "#/components/schemas/FileBuild"
          }
        }
      },
      "FileList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/File"
            }
          },
          "nextCursor": {
            "type": "string",
            "description": "Pass as `cursor` for the next page. Absent on the last page."
          }
        }
      },
      "FileUsage": {
        "type": "object",
        "required": [
          "usedBytes",
          "quotaBytes"
        ],
        "properties": {
          "usedBytes": {
            "type": "integer",
            "format": "int64",
            "description": "Sum of the organization's stored files, failed files excluded."
          },
          "quotaBytes": {
            "type": "integer",
            "format": "int64",
            "nullable": true,
            "description": "The plan's storage quota; null when unlimited. Shown, not enforced yet."
          }
        }
      },
      "UpdateFileRequest": {
        "type": "object",
        "description": "Absent fields are left unchanged.",
        "properties": {
          "expiresIn": {
            "type": "integer",
            "format": "int64",
            "minimum": 600,
            "maximum": 7776000,
            "description": "Seconds from now: 10 minutes to 90 days. Restarts the clock and may shorten the expiry (a create never does)."
          },
          "name": {
            "type": "string",
            "maxLength": 255,
            "description": "Display name. An empty string clears it, so the file shows its filename again."
          }
        }
      },
      "FileDownload": {
        "type": "object",
        "required": [
          "url",
          "expiresAt"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Presigned download URL; the browser saves it under the file's original filename."
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the URL stops working (5 minutes)."
          }
        }
      },
      "AppLatestFile": {
        "type": "object",
        "description": "The app's newest ready, unexpired file.",
        "required": [
          "id",
          "name",
          "version",
          "buildNumber",
          "createdAt",
          "expiresAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "version": {
            "type": "string",
            "nullable": true,
            "example": "4.2.0"
          },
          "buildNumber": {
            "type": "string",
            "nullable": true,
            "example": "4201"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "App": {
        "type": "object",
        "required": [
          "id",
          "platform",
          "bundleId",
          "name",
          "iconUrl",
          "fileCount",
          "latestFile",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "platform": {
            "type": "string",
            "enum": [
              "android",
              "ios"
            ]
          },
          "bundleId": {
            "type": "string",
            "example": "com.acme.shop"
          },
          "name": {
            "type": "string",
            "nullable": true,
            "description": "Label of the latest file that had one.",
            "example": "Acme Shop"
          },
          "iconUrl": {
            "type": "string",
            "nullable": true,
            "description": "Relative URL of the icon (getAppIcon); null when no icon could be extracted.",
            "example": "/api/v1/apps/7d2a90c1-5b0e-4f7a-8c11-2e9f0a4b6d33/icon"
          },
          "fileCount": {
            "type": "integer",
            "description": "Ready, unexpired files."
          },
          "latestFile": {
            "type": "object",
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/AppLatestFile"
              }
            ],
            "description": "Newest ready file, whoever uploaded it and whatever its branch. Null only when every file has expired."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AppList": {
        "type": "object",
        "required": [
          "object",
          "data"
        ],
        "properties": {
          "object": {
            "type": "string",
            "enum": [
              "list"
            ]
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/App"
            }
          },
          "nextCursor": {
            "type": "string",
            "description": "Pass as `cursor` for the next page. Absent on the last page."
          }
        }
      },
      "InstallFileRequest": {
        "type": "object",
        "required": [
          "file"
        ],
        "properties": {
          "file": {
            "type": "string",
            "format": "uuid",
            "description": "A ready file of the organization (see Files)."
          },
          "launchAfterInstall": {
            "type": "boolean",
            "default": false,
            "description": "Launch the app once it is installed. A failed launch does not fail the install; see `launched`."
          }
        }
      },
      "InstallFileResult": {
        "type": "object",
        "required": [
          "file",
          "status",
          "bundleId",
          "launched"
        ],
        "properties": {
          "file": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "installed"
            ]
          },
          "bundleId": {
            "type": "string",
            "example": "com.acme.shop"
          },
          "launched": {
            "type": "boolean",
            "description": "True when launchAfterInstall was set and the launch succeeded."
          }
        }
      }
    }
  }
}
