REST API

All endpoints are under /api/v1. This page covers the public Bearer-token API for automation and server-side tooling. Requests and responses use JSON.

Authentication

Public REST requests use a Bearer token:

Bearer tokenCLI / server operations — upload, release, list. Use your organization secret key (otakit_sk_...) or a user access token from otakit login. The app ID is part of the URL path.
# CLI / server operations
Authorization: Bearer otakit_sk_...

Apps

POST/api/v1/apps

Create a new app. The slug should match your Capacitor app identifier. Returns an appId for plugin and CLI usage.

Auth: Bearer

Request body

{ "slug": "com.example.myapp" }

Response

{
  "id": "uuid",
  "slug": "com.example.myapp",
  "createdAt": "ISO timestamp"
}

Bundles

POST/api/v1/apps/:appId/bundles/initiate

Start a bundle upload session. Returns a presigned PUT URL. Upload your zip file to this URL with Content-Type: application/zip.

Auth: Bearer

Request body

{
  "version": "1.0.1",       // semver string
  "size": 1048576,          // bundle size in bytes
  "sha256": "64-char hex checksum of the zip file",
  "runtimeVersion": "2026.04" // optional compatibility lane
}

Response

{
  "uploadId": "uuid",
  "presignedUrl": "https://...",
  "storageKey": "...",
  "expiresAt": "ISO timestamp"
}
POST/api/v1/apps/:appId/bundles/finalize

Finalize a bundle upload session. The server checks that the uploaded object exists and that its size matches the initiated session, then creates the bundle record from the stored session data.

Auth: Bearer

Request body

{
  "uploadId": "uuid"
}

Response

{
  "id": "uuid",
  "version": "1.0.1",
  "sha256": "...",
  "size": 1048576,
  "runtimeVersion": "2026.04",
  "createdAt": "ISO timestamp"
}
GET/api/v1/apps/:appId/bundles

List bundles sorted by creation date (newest first).

Auth: Bearer · Query: ?limit=20&offset=0

Response

{
  "bundles": [{ id, version, sha256, size, createdAt }],
  "total": 42
}
DELETE/api/v1/apps/:appId/bundles/:bundleId

Delete a bundle. Bundles that are part of a release history cannot be deleted.

Auth: Bearer

Response

{ "deleted": true, "id": "uuid" }

Releases

POST/api/v1/apps/:appId/releases

Release a bundle to the base channel or a named channel. The runtimeVersion comes from the bundle itself, so current resolution is per (channel, runtimeVersion). A rolloutPercent below 100 releases to that share of devices; it needs a previous release on the lane. While a rollout is active, another release returns 409 ROLLOUT_IN_PROGRESS unless replaceRollout is true.

Auth: Bearer

Request body

{
  "bundleId": "uuid",
  "channel": "staging",    // optional; omit or null for base channel
  "rolloutPercent": 10,    // optional; 1-100, default 100
  "replaceRollout": true   // optional; cancel the active rollout first
}

Response

{
  "release": {
    "id": "uuid",
    "channel": null,
    "runtimeVersion": "2026.04",
    "bundleId": "uuid",
    "bundleVersion": "1.0.1",
    "rolloutPercent": 10,
    "promotedAt": "ISO timestamp"
  },
  "previousRelease": { ... } | null
}
PATCH/api/v1/apps/:appId/releases/:releaseId/rollout

Change the percentage of an active rollout. Only the current release of a lane can change while it is below 100%, and a completed rollout stays complete. To cancel a rollout, revert the release: POST /api/v1/apps/:appId/releases/:releaseId/revert, optionally with expectedRolloutPercent so the revert is refused if the rollout completed or changed meanwhile.

Auth: Bearer

Request body

{
  "percent": 50,           // 1-100; 100 completes the rollout
  "expectedPercent": 10    // optional; rejects the change if the rollout moved
}

Response

{
  "release": { ..., "rolloutPercent": 50 },
  "previousPercent": 10,
  "publicationStatus": "published"
}
GET/api/v1/apps/:appId/releases

List release history sorted newest first. Omit channel to list every stream, or pass an empty channel value to query only the base channel.

Auth: Bearer · Query: ?channel=staging&limit=20&offset=0

Response

{
  "releases": [{
    id, channel, runtimeVersion, bundleId, bundleVersion, promotedAt
  }],
  "total": 12
}

Preview links

POST/api/v1/apps/:appId/bundles/:bundleId/previews

Create a preview link that opens the bundle in the installed app, without releasing it. Share url or qrUrl; deepLink is null until the app's URL scheme is known. An app can have 20 active links (409 PREVIEW_LIMIT_REACHED).

Auth: Bearer

Request body

{
  "expiresIn": "24h",      // optional; 1h, 24h, 7d (default) or 30d
  "urlScheme": "myapp"     // optional; remembered for the app
}

Response

{
  "preview": {
    "id": "uuid",
    "bundleId": "uuid",
    "bundleVersion": "1.0.1",
    "runtimeVersion": "2026.04",
    "expiresAt": "ISO timestamp",
    "url": "https://console.otakit.app/p/<token>",
    "qrUrl": "https://console.otakit.app/p/<token>/qr.png",
    "deepLink": "myapp://otakit-preview?token=<token>" | null
  }
}
GET/api/v1/apps/:appId/previews

List active preview links, newest first, optionally for one bundle.

Auth: Bearer · Query: ?bundleId=uuid

Response

{
  "previews": [{ id, bundleId, bundleVersion, expiresAt, url, qrUrl, deepLink, ... }],
  "urlScheme": "myapp" | null
}
DELETE/api/v1/apps/:appId/previews/:previewId

Revoke a preview link. Phones on it return to their release on their next update check.

Auth: Bearer

Response

{ "status": "revoked" | "already_ended", "previewId": "uuid" }