# Access & the raw API
URL: https://openship.io/docs/cli/access.md

Log in, switch between instances with contexts, manage personal access tokens, and call any API route directly with openship api.

These commands are how the CLI knows *which* Openship it's talking to and *who* you are — plus an escape
hatch (`openship api`) for reaching any route that doesn't have its own command yet.

<Callout title="Where auth lives">
The CLI keeps its settings in **`~/.openship/config.json`** (mode `0600`). That file holds one or more named
**contexts** — each pins an API URL, a dashboard URL, and a personal access token (a `opsh_pat_…` string).
One context is *active* at a time; every authenticated command reads from it. The default context is called
**`default`** and points at your local instance (`http://localhost:4000`).
</Callout>

<Callout title="JSON output" type="info">
`--json` is a global flag — put it **before** the subcommand (`openship --json token list`). In JSON mode the
data is written to stdout as JSON while status messages go to stderr, so a piped stream stays clean. Setting
`OPENSHIP_JSON=1` in the environment does the same thing. `openship api` always prints the response body.
</Callout>

## `openship login`

Authenticate against an instance and save the token into a context. Run with no `--token` and it opens the
dashboard's **Settings** page — where Personal Access Tokens live — and waits for you to paste one; pass
`--token` to log in non-interactively (CI, scripts). The token is validated against the API before it's stored, and it must
start with `opsh_pat_`.

```bash
# Interactive — opens the dashboard, prompts for a pasted token
openship login

# Non-interactive — against a remote self-hosted instance, stored as a named context
openship login \
  --token opsh_pat_xxxxxxxx \
  --api-url https://openship.example.com \
  --dashboard-url https://openship.example.com \
  --context prod
```

| Flag | Purpose | Default |
|---|---|---|
| `--token <token>` | Personal access token (`opsh_pat_…`) for non-interactive login. | *(prompted)* |
| `--api-url <url>` | API base URL to authenticate against. | `http://localhost:4000` |
| `--dashboard-url <url>` | Dashboard base URL (used to open the token page). | `http://localhost:3001` |
| `--context <name>` | Name of the context to store this login under. | `default` |

<Callout title="Can't reach the API?" type="warn">
If login reports it couldn't reach the API, the host in `--api-url` isn't responding. Point it at the right
instance — for a remote self-hosted box that's the same URL you open in the browser.
</Callout>

## `openship logout`

Remove the stored token from a context. The endpoints stay saved, so a later `login` just re-adds the token.

```bash
openship logout                 # active context
openship logout --context prod  # a specific one
```

| Flag | Purpose | Default |
|---|---|---|
| `--context <name>` | Log out of a specific context. | *(active context)* |

## `openship context`

Manage the named connections in `~/.openship/config.json`. This is pure local config editing — no API calls.
Use it to keep several instances side by side (local, staging, production) and switch between them. Alias:
`openship ctx`. Running it bare prints the active context and the full list (the active row is starred).

```bash
openship context           # show active + list all
openship context use prod  # switch the active context
```

| Subcommand | Purpose |
|---|---|
| `list` (alias `ls`) | List configured contexts. |
| `use <name>` | Switch the active context. |
| `add <name>` | Create or update a context's endpoints/token. |
| `rm <name>` (alias `remove`) | Remove a context (can't remove the active one — switch away first). |

The list shows one row per context: `current` (`*` marks the active one), `name`, `apiUrl`,
`dashboardUrl`, and `auth` (`token` when a token is stored, `-` when not).

### `context add`

Register an instance without logging in interactively. `add` only writes config — use `login` when you want
the token checked against the API first.

```bash
openship context add staging \
  --api-url https://staging.example.com \
  --dashboard-url https://staging.example.com \
  --token opsh_pat_xxxxxxxx \
  --use
```

| Flag | Purpose |
|---|---|
| `--api-url <url>` | API base URL. |
| `--dashboard-url <url>` | Dashboard base URL. |
| `--token <token>` | Personal access token to store. |
| `--use` | Switch to this context after adding. |

## `openship token`

Create and manage the personal access tokens that authenticate the CLI and the API. Tokens are
**self-scoped** — every call operates on your own tokens. In the dashboard these live under
**Settings → Personal Access Tokens**.

| Subcommand | Purpose |
|---|---|
| `list` | List your personal access tokens. |
| `create <name>` | Mint a new token (the secret is shown once). |
| `revoke <id>` | Revoke one of your tokens. |

### `token create`

```bash
# A full-access, non-expiring token
openship token create "laptop"

# A read-only token that expires in 30 days
openship token create "ci-reader" --read-only --expires 30

# Scope a token to one project with specific permissions (repeatable)
openship token create "deploy-bot" --grant project:abc123:read,write
```

| Flag | Purpose | Default |
|---|---|---|
| `--read-only` | Reject mutation methods (`POST`/`PUT`/`PATCH`/`DELETE`). | off |
| `--expires <days>` | Expire after N days (1–365); omit for a non-expiring token. | never |
| `--grant <type:id:perms>` | Scope the token to a resource. Repeatable. Format `type:id:perm1,perm2`, e.g. `project:abc123:read,write`. | *(full access)* |

<Callout title="Copy the secret immediately" type="warn">
`token create` prints the full `opsh_pat_…` secret **once** and never again. `token list` only ever shows a
short prefix. If you lose it, revoke the token and mint a new one.
</Callout>

The `list` table shows: `id`, `name`, `prefix`, `readOnly`, `scoped`, `expires`, `lastUsed`, and `revoked`.
Pass a token's `id` (they look like `pat_…`) to `revoke`:

```bash
openship token list
openship token revoke pat_abc123
```

## `openship api`

The escape hatch. Make an authenticated request to **any** API route using the active context's token —
much like `gh api`. This is what keeps the CLI small: anything without a dedicated command (audit log,
one-off reads, routes added after your CLI version) is still reachable here.

The `<path>` argument is relative to `/api`, so `openship api /projects` calls
`<your-api-url>/api/projects`. The method defaults to `GET`, or `POST` when you pass `--data`.

```bash
# GET a collection
openship api /projects

# GET a single resource
openship api /deployments/dep_abc123

# POST with a JSON body (method defaults to POST when --data is given)
openship api /domains -d '{"projectId":"proj_123","hostname":"app.example.com"}'

# DELETE with an explicit method
openship api /tokens/pat_abc123 -X DELETE

# Add query parameters (repeatable)
openship api /domains -q projectId=proj_123
```

| Flag | Purpose | Default |
|---|---|---|
| `-X, --method <method>` | HTTP method. | `GET`, or `POST` when `--data` is set |
| `-d, --data <json>` | Request body as a JSON string. | *(none)* |
| `-q, --query <kv...>` | Query parameter `key=value`. Repeatable. | *(none)* |

The response body is printed to stdout — pretty-printed when it's JSON, raw otherwise. On a non-2xx
response the body is still printed and the command exits non-zero, so you can pipe it into `jq` and still
detect failures in scripts.

<Callout title="Which routes exist?" type="info">
Browse the [API reference](/docs/api) for paths, methods, and required permissions. The resource commands
([`project`, `service`, `domain`, and friends](/docs/cli/projects)) wrap the common ones in a friendlier
form — reach for `openship api` when there's no dedicated command.
</Callout>
