API

Service terminal

Issue access tickets and open a service shell.

The Service terminal API gives you a live shell inside a service's running container — the same PTY you get from the dashboard's Terminal tab on a service. You first mint a short-lived ticket over normal HTTP, then open a WebSocket that carries the ticket and streams keystrokes and output. It works in both deployment modes: on self-hosted instances it's a docker exec into the container; on Openship Cloud it's a shell in your Oblien workspace. The runtime is chosen automatically from the service's active deployment.

Mint a ticket

POST /api/services/terminal/ticket

Prop

Type

Returns { success: true, token, expiresIn }. The token is single-use and expires after expiresIn seconds — mint it right before opening the socket. This route resolves the service and 404s early (before you burn a WebSocket attempt) if it doesn't exist, isn't yours, or has no active deployment yet.

curl -X POST "$OPENSHIP_URL/api/services/terminal/ticket" \
  -H "Authorization: Bearer $OPENSHIP_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"serviceId":"svc_123"}'

CLI equivalent

There's no working CLI shell yet — openship service exec is a stub that prints "coming soon" (the CLI ships no WebSocket client). Use the dashboard's service Terminal tab, which drives these endpoints.

Open the WebSocket

GET /api/services/terminal/ws/:serviceId

Connect a WebSocket to this path. Because HTTP middleware that returned 401 would abort the handshake, this route is public at the HTTP layer and authenticates inside the upgrade instead. Two things gate it:

  • Origin — the request Origin must be in the instance's trusted-origins allowlist, or the socket closes with 4403.
  • Identity — pass the ticket from /ticket as a WebSocket subprotocol, prefixed with openship.terminal.v1+ (e.g. openship.terminal.v1+<token>). If no ticket is present, Openship falls back to the session cookie (dashboard case). To resume a parked session, add a second subprotocol prefixed with openship.terminal.resume+<resumeToken>.

Once open, the wire protocol is:

  • Binary frames are raw PTY bytes — send keystrokes as binary; stdout/stderr arrive as binary.
  • Text frames are JSON control messages. Client → server: {"type":"resize","cols":N,"rows":N} (cols clamped 1–1000, rows 1–500), {"type":"ping"}, {"type":"close"}. Server → client: {"type":"ready","sessionId","resumeToken","resumed"} on connect, {"type":"exit","code","signal"} when the shell ends, {"type":"error","code","message"}, and {"type":"pong"} (also sent as a heartbeat every ~25s).
# Mint a ticket, then connect with websocat, passing it as a subprotocol:
websocat "wss://your-host/api/services/terminal/ws/svc_123" \
  --protocol "openship.terminal.v1+$TICKET" \
  -H "Origin: $OPENSHIP_URL"

Errors

The WebSocket signals failures by sending an {"type":"error"} frame and then closing with a specific code:

4401 — unauthorized

No valid ticket or session, no active organization, or the ticket's service doesn't match the path :serviceId. Mint a fresh ticket (they're single-use and short-lived) and connect immediately.

4404 — service not found or not deployed

The service isn't in your active organization, you lack admin on its project, or it has no running container yet (still deploying, or the project has no active deployment). List services with the Services API to confirm it's live.

4400 — terminal not supported

The service's runtime doesn't offer an interactive shell (not_supported), or the :serviceId was missing.

4429 — too many sessions

You've hit the per-user concurrent-session cap. Close an existing terminal and retry.

4403 — origin not allowed

The request Origin isn't in the trusted-origins allowlist. Connect from the dashboard's own host.

Operations

SDK and CLI

Use client.terminal.openService(serviceId, { origin, onData }) for the same authenticated terminal protocol as server terminals. The session supports write, resize, close, and a closed promise with the confirmed exit result. See terminal transport.

openship service terminal <id> --project <project-id> opens an interactive PTY. Use openship service exec <id> "..." --project <project-id> for scripts; it preserves the remote command's exit status.

OperationSDKREST API
Mint a one-shot, short-lived WebSocket auth ticket for a service.terminal.openService(id, options)
Remote client
POST /api/services/terminal/ticket
terminal:write
WebSocket upgrade — auth happens inside the handshake (ticket subprotocol or session cookie).terminal.openService(id, options)GET /api/services/terminal/ws/:serviceId
Handler authentication

On this page