---
title: "Catalog and discovery"
description: "Browse the server catalog, search it by intent, and manage per-user provider connections from the terminal."
---

import { Callout } from "fumadocs-ui/components/callout";

Eight commands cover the catalog and the per-user connections behind it: `servers list`, `servers show`, `tools list`, `discover`, `connect list`, `connect start`, `connect revoke` and `wizard`. None of them moves money, and only `connect start` and `connect revoke` change anything. Six go straight to a REST route. `discover` and `wizard` take a longer path: they open a session, run a meta-tool inside it, and close the session on the way out.

Every flag, message and exit code on this page was run against `@codespar/cli` 0.10.0 — the version on npm — and the transcripts are the captured bytes of those runs.


## What every command here shares

### Credentials and scope

Resolution order, first match wins: the root flag, then the environment variable, then `~/.codespar/config.json`.

| Setting | Root flag | Environment | Config key |
|---|---|---|---|
| API key | `--api-key <key>` | `CODESPAR_API_KEY` | `apiKey` |
| API host | `--base-url <url>` | `CODESPAR_BASE_URL` | `baseUrl` |
| Project | `--project <id>` | `CODESPAR_PROJECT` | `project` |

With nothing set, the host is `https://api.codespar.dev`. On the six commands that call REST directly, a resolved project rides as the `x-codespar-project` header and an unresolved one leaves the header off. `discover` and `wizard` hand the project to the SDK constructor instead.

Without a key, every command on this page refuses before it opens a socket:

```bash
codespar servers list
```

```text title="stderr, exit 1"
✗ Not logged in. Run `codespar login` or set CODESPAR_API_KEY.
```

That line is identical on all eight, and `discover` and `wizard` give it too: the key is resolved before the SDK is constructed. A key the API rejects is a different story, and the per-command tables below carry it.

### stdout, stderr and `--json`

Tables, key/value blocks and JSON go to stdout. Status lines go to stderr, prefixed `ℹ`, `✓` or `⚠`, and an empty table prints `(no results)` there too. A pipe into `jq` therefore receives the data alone.

`--json` is declared on the root command, but Commander accepts it on either side of the subcommand: `codespar --json servers list` and `codespar servers list --json` both printed the same JSON with an empty stderr. Colour is dropped when stdout is not a TTY, when `NO_COLOR` is set, or when `FORCE_COLOR=0`.

### Exit codes

| Code | When | First line of stderr |
|---|---|---|
| `0` | The command finished, including `--help` | nothing |
| `1` | A refusal the CLI raises itself, or an argument Commander rejects | `✗ <message>`, or `error: <message>` for Commander's own |
| `2` | Any other exception. The stack trace follows | `✗ internal error:` |

<Callout type="warning">
`discover` and `wizard` reach the API through the SDK, and the SDK throws a plain `Error`, not the CLI's own error type. Measured: a `401` or a `403` on either command comes out as `✗ internal error:` plus a stack trace and **exit 2**, where the six REST commands give exit 1 and one line. A tool call that answers `success: false` does the same. On these two commands, exit 2 is not evidence of a bug in the CLI.
</Callout>

### The order they run in

<EventPipeline
  title="CATALOG ORDER"
  subtitle="Find a provider, read what it exposes, then connect"
  nodes={[
    { tag: "1 · FIND", name: "codespar discover <query>", meta: "ranked matches · CONN connected, disconnected or not_required", tone: "event" },
    { tag: "2 · INSPECT", name: "codespar servers show <id>", meta: "codespar tools list --server <id> for the full tool set", tone: "validation" },
    { tag: "3 · REQUIREMENTS", name: "codespar wizard <server-id>", meta: "--action initiate · required secrets, instructions, pitfalls", tone: "validation" },
    { tag: "4 · CONNECT", name: "codespar connect start <server>", meta: "prints the authorize URL the end user has to visit", tone: "loop" },
  ]}
/>

Skipping the middle two steps costs the answers only inspection gives. `discover` prints a `CONN` column per match, and `not_required` there means the tool answers today with no connection behind it. `wizard` names the secrets and the steps a provider takes before you send an end user to a link, while `connect start` passes your argument straight through as `server_id` and asks nothing else of you.

## `codespar servers list`

Lists the server catalog your key can see, one row per server, filtered server-side by whatever you pass.

<Endpoint method="GET" path="/v1/servers" base="https://api.codespar.dev" />

| Name | Type | Required | What it does |
|---|---|---|---|
| `-c, --category <name>` | `string` | no | Sent as the `category` query parameter. The CLI does not validate the value |
| `-r, --region <code>` | `string` | no | Sent as the `region` query parameter. The flag's help names `BR` and `MX` as examples; any string is passed through |
| `--json` | `boolean` | no | Root flag. Prints the array inside the response envelope, not the envelope |

Measured wire: `codespar servers list -c payments -r BR` issued `GET /v1/servers?category=payments&region=BR`. An absent filter is left off the query string, never sent empty.

<Split min={380}>
<SplitPane label="Command">

```bash
codespar servers list
```

</SplitPane>
<SplitPane label="stdout">

```text title="exit 0"
ID          NAME        CATEGORY  REGION  TOOLS  STATUS
asaas       Asaas       payments  BR      12     live
stark-bank  Stark Bank  banking   BR      8      live
pomelo      Pomelo      issuing   -       -      beta
```

</SplitPane>
</Split>

A column with no value prints `-`. With no rows at all, stdout is empty and stderr carries `(no results)`.

**Calls** `GET /v1/servers`, documented at [GET /v1/servers in the HTTP reference](/docs/api/reference/servers#get-v1servers).

| When | What you get | Exit |
|---|---|---|
| No API key resolved | ``✗ Not logged in. Run `codespar login` or set CODESPAR_API_KEY.`` | 1 |
| Key rejected or under-scoped | `✗ GET /v1/servers → 403: <the API's own message>` | 1 |
| Host unreachable, or slower than 30s | `✗ Network error calling GET /v1/servers: <cause>`, or `✗ Request to GET /v1/servers timed out after 30000ms.` | 1 |

## `codespar servers show <id>`

Reads one server's detail record and prints its metadata, its description and the tools it exposes.

<Endpoint method="GET" path="/v1/servers" base="https://api.codespar.dev" />
<Endpoint method="GET" path="/v1/servers/{id}/tools" base="https://api.codespar.dev" />
<Endpoint method="GET" path="/v1/servers/{id}/auth-schema" base="https://api.codespar.dev" />

| Name | Type | Required | What it does |
|---|---|---|---|
| `<id>` | `string` | yes | Positional. Percent-encoded into the path |
| `--json` | `boolean` | no | Root flag. Prints the response body as received |


<Split min={380}>
<SplitPane label="Command">

```bash
codespar servers show asaas
```

</SplitPane>
<SplitPane label="stdout">

```text title="exit 0"
ID        asaas
Name      Asaas
Category  payments
Region    BR
Status    live
Auth      api_key
Tools     12

Brazilian payment provider: Pix, boleto and card acquiring.

Tools:
  • codespar_charge — Issue an inbound charge
  • codespar_pay — Send an outbound payment
```

</SplitPane>
</Split>

The `Tools` count prefers the record's own count and falls back to the length of the returned tool list. The description paragraph and the `Tools:` block are printed only when the response carries them.

| When | What you get | Exit |
|---|---|---|
| `codespar servers show` with no id | `error: missing required argument 'id'` | 1 |
| Unknown id | `✗ GET /v1/servers/<id> → 404: <the API's own message>` | 1 |
| Key rejected or under-scoped | `✗ GET /v1/servers/<id> → 403: <the API's own message>` | 1 |
| No API key resolved | ``✗ Not logged in. Run `codespar login` or set CODESPAR_API_KEY.`` | 1 |

## `codespar tools list`

Lists the tools of one server, with each description cut to fit a terminal column.

<Endpoint method="GET" path="/v1/servers/{id}/tools" base="https://api.codespar.dev" />

| Name | Type | Required | What it does |
|---|---|---|---|
| `-s, --server <id>` | `string` | **yes** | The server whose tools to list. It is the address of the listing, not a filter |
| `--json` | `boolean` | no | Root flag. Prints the array with descriptions at full length |

<Callout type="info">
**Changed in 0.8.0.** `--server` used to be an optional filter and is now required: the API lists tools per server, so the server is the address of the listing. Without it the command refuses without touching the network, and names the two commands that lead to an id.
</Callout>

<Split min={380}>
<SplitPane label="Command">

```bash
codespar tools list --server asaas
```

</SplitPane>
<SplitPane label="stdout">

```text title="exit 0"
NAME                 DESCRIPTION
cancel_subscription  Cancel an active subscription.
create_customer      Create a customer. Required before creating any payment — Asaas requi…
create_payment       Create a payment charge (Pix, boleto, or credit card) linked to a cus…

24 tool(s) on asaas.
```

</SplitPane>
</Split>

The `DESCRIPTION` column is truncated to 70 characters; `--json` carries the full text.

**Calls** [GET /v1/servers/\{id\}/tools](/docs/api/reference/servers#get-v1serversidtools), which the HTTP reference documents.

| When | What you get | Exit |
|---|---|---|
| No `--server` | The refusal above, naming `codespar servers list` and `codespar tools meta`. Nothing is sent | 1 |
| Unknown server id | `✗ GET /v1/servers/<id>/tools → 404: <the API's own message>` | 1 |
| Key rejected | `✗ GET /v1/servers/<id>/tools → 401: <the API's own message>` | 1 |
| No API key resolved | ``✗ Not logged in. Run `codespar login` or set CODESPAR_API_KEY.`` | 1 |

## `codespar discover <query>`

Searches the catalog by intent instead of by name: it opens a session attached to no server, runs the `codespar_discover` meta-tool inside it, prints the ranked matches and closes the session.

<Endpoint method="POST" path="/v1/sessions/{id}/execute" base="https://api.codespar.dev" />

| Name | Type | Required | What it does |
|---|---|---|---|
| `<query>` | `string` | yes | Positional. Sent as `use_case` |
| `--limit <n>` | `integer` | no | Must parse as a positive integer or the command refuses. Sent as `limit`, and used again locally as the number of rows printed. With the flag absent the CLI prints at most 10 and sends no limit |
| `--category <name>` | `string` | no | Sent as `category` when present |
| `--country <code>` | `string` | no | Sent as `country` when present. The flag's help names `BR` and `MX` as examples |
| `-u, --user <id>` | `string` | no | The session's `user_id`. Defaults to the literal `cli-user`. The default lives in the command body, not in Commander, so `--help` does not print it |
| `--json` | `boolean` | no | Root flag. Prints the whole result object, not the table's five columns |

Measured wire, in order: `POST /v1/sessions` with `{"servers":[],"user_id":"cli-user"}`, then `POST /v1/sessions/{id}/execute` with `{"tool":"codespar_discover","input":{"use_case":"…","category":"…","country":"…","limit":5}}`, then `DELETE /v1/sessions/{id}`. The session is closed in a `finally`, so it is closed even when the call fails.

<Split min={380}>
<SplitPane label="Command">

```bash
codespar discover "issue an NF-e for a service"
```

</SplitPane>
<SplitPane label="stdout and stderr, interleaved">

```text title="exit 0"
ℹ Strategy: embedding  ·  use_case: "issue an NF-e for a service"
#  SCORE  SERVER.TOOL                CONN          DESCRIPTION
1  0.887  codespar.codespar_invoice  connected     Issue an NF-e / NFS-e fiscal document after settlement.
2  0.609  codespar.codespar_charge   not_required  Issue an inbound charge so there is something to invoice.

ℹ Next steps:
  - Connect a fiscal provider
  - Call codespar_invoice with the charge id
```

</SplitPane>
</Split>

Three things the table does not say for itself:

- **`SCORE` is computed by the CLI, not returned by the API.** It is `1 - cosine_distance` when a cosine distance is present, the trigram similarity when it is not, and `-` when neither is, always to three decimals. The stub's `cosine_distance: 0.1132` printed as `0.887`.
- **The `Strategy` line and the `Next steps:` label go to stderr; the table and the step items go to stdout.** The label and its list are split across the two channels.
- **`CONN` is the match's connection status**, one of `connected`, `disconnected` or `not_required`, taken from the SDK's own type. It is what tells you whether the tool you just found is callable today or needs a connection first.

With no match at all, stderr carries `(no matches)` and stdout stays empty.

**Calls** the `codespar_discover` meta-tool over [POST /v1/sessions/\{id\}/execute](/docs/api/reference/sessions#post-v1sessionsidexecute). The tool's own arguments and result are documented at [`codespar_discover`](/docs/concepts/meta-tools/discover).

| When | What you get | Exit |
|---|---|---|
| `codespar discover` with no query | `error: missing required argument 'query'` | 1 |
| `--limit 0`, `--limit abc`, any non-positive or unparseable value | `✗ --limit must be a positive integer.` | 1 |
| No API key resolved | ``✗ Not logged in. Run `codespar login` or set CODESPAR_API_KEY.`` | 1 |
| Key rejected or under-scoped | `✗ internal error:` and a stack trace naming `createSession failed: 403 …` | 2 |
| The tool call answers `success: false` | `✗ internal error:` and a stack trace naming `discover failed: <the tool's message>` | 2 |

## `codespar connect list`

Lists the per-user provider connections your project holds, with the dates cut to the day.

<Endpoint method="GET" path="/v1/connections" base="https://api.codespar.dev" />

| Name | Type | Required | What it does |
|---|---|---|---|
| `-u, --user <id>` | `string` | no | Sent as the `user_id` query parameter. Unset means every user |
| `--status <s>` | `string` | no | Sent as `status`. The flag's help names `connected`, `pending`, `revoked` and `expired`; the CLI validates nothing and passes the string through |
| `--json` | `boolean` | no | Root flag. Prints the array inside the envelope, with full timestamps |

This route answers a `connections` envelope where `servers list` and `tools list` get a `data` envelope. It matters only if you read the raw HTTP; `--json` unwraps it either way.

<Split min={380}>
<SplitPane label="Command">

```bash
codespar connect list --user user_42 --status connected
```

</SplitPane>
<SplitPane label="stdout">

```text title="exit 0"
ID         SERVER       USER     STATUS     CONNECTED   EXPIRES
ca_7Fq2xb  asaas        user_42  connected  2026-09-02  -
ca_9Kd0mn  mercadopago  user_42  connected  2026-09-09  2026-12-08
```

</SplitPane>
</Split>

`CONNECTED` and `EXPIRES` are the first ten characters of the ISO timestamp, so the time of day is visible only under `--json`. A null date prints `-`.

**Calls** `GET /v1/connections`, documented at [GET /v1/connections in the HTTP reference](/docs/api/reference/connections#get-v1connections).

| When | What you get | Exit |
|---|---|---|
| Key rejected or under-scoped | `✗ GET /v1/connections → 403: <the API's own message>` | 1 |
| No API key resolved | ``✗ Not logged in. Run `codespar login` or set CODESPAR_API_KEY.`` | 1 |
| No connection matches the filters | `(no results)` on stderr, empty stdout | 0 |

## `codespar connect start <server>`

Opens a Connect Link flow for one end user and prints the authorize URL they have to visit.

<Endpoint method="POST" path="/v1/connect/start" base="https://api.codespar.dev" />

| Name | Type | Required | What it does |
|---|---|---|---|
| `<server>` | `string` | yes | Positional. Sent as `server_id` in the body |
| `-u, --user <id>` | `string` | no | Sent as `user_id`. Defaults to the literal `cli-user`, a default that lives in the command body and not in `--help` |
| `-r, --redirect-uri <url>` | `string` | no | Sent as `redirect_uri`. Defaults to `http://localhost:3000/connect/success` so the flow completes without a hosted page of your own |
| `--scopes <scopes>` | `string` | no | Sent as `scopes` when present, omitted from the body when absent. Passed through as one string |
| `--open` | `boolean` | no | Ask for the link to be opened in the system browser. See the note below: it is a request, not a guarantee |
| `--no-open` | `boolean` | no | Never open the browser. Print the link only |
| `--json` | `boolean` | no | Root flag. Prints the response body and nothing else |

Measured body: `{"server_id":"asaas","user_id":"user_42","redirect_uri":"https://app.exemplo.com.br/connect/done","scopes":"payments:read"}`.

<Split min={380}>
<SplitPane label="Command">

```bash
codespar connect start asaas --user user_42
```

</SplitPane>
<SplitPane label="stdout and stderr, interleaved">

```text title="exit 0"
ℹ Connect asaas for user user_42:

  https://connect.codespar.dev/l/lnk_3Bq8

ℹ Link expires 13/09/2026, 15:00:00
ℹ Tip: pass --open on a future run to launch the link automatically.
```

</SplitPane>
</Split>

The authorize URL is the only thing on stdout: a blank line, the URL indented by two spaces, a blank line. The expiry is rendered in the machine's own locale and timezone, so it is not a stable string to parse; `--json` carries the `expires_at` instant.

<Callout type="warning">
`--open` opens the browser only when stdout is a TTY, despite its help text reading "Force-open the link". Measured with stdout in a pipe: `--open` opened nothing, and a bare run and `--no-open` printed byte-identical output. The one visible difference in a pipe is that `--open` suppresses the `Tip:` line. The TTY branch itself was not exercised, because exercising it opens a real browser.
</Callout>

Before the OS opener is called, the URL is parsed and checked to be `http` or `https`, then passed as a literal argument with no shell in between, so a link carrying shell metacharacters cannot run a command. Both refusals, the malformed URL and the non-http scheme, are swallowed by a `catch` in this command: nothing is printed and the exit code stays 0. The URL is on stdout either way, which is what the fallback assumes.

**Calls** `POST /v1/connect/start`, documented at [POST /v1/connect/start in the HTTP reference](/docs/api/reference/connect#post-v1connectstart). The concept behind it is [Connect Links](/docs/concepts/connect-links).

| When | What you get | Exit |
|---|---|---|
| `codespar connect start` with no server | `error: missing required argument 'server'` | 1 |
| Key rejected or under-scoped | `✗ POST /v1/connect/start → 403: <the API's own message>` | 1 |
| No API key resolved | ``✗ Not logged in. Run `codespar login` or set CODESPAR_API_KEY.`` | 1 |

## `codespar connect revoke <connection-or-server>`

Revokes one connection, taking either the connection id or a server id it resolves on your behalf.

<Endpoint method="POST" path="/v1/connections/{id}/revoke" base="https://api.codespar.dev" />

| Name | Type | Required | What it does |
|---|---|---|---|
| `<connection-or-server>` | `string` | yes | Positional. An argument starting with `ca_` is treated as a connection id and revoked directly. Anything else is treated as a server id and resolved first |
| `-u, --user <id>` | `string` | no | Only used on the resolve path. Defaults to the literal `cli-user` |

The resolve path costs one extra request: `GET /v1/connections?user_id=<user>&server_id=<arg>&status=connected`, and then the **first** row of that list is the one revoked. Measured: `codespar connect revoke asaas -u user_42` issued that query and then `POST /v1/connections/ca_7Fq2xb/revoke`.

<Callout type="warning">
When a user holds more than one active connection to the same server, the first row of the list wins and the output names only the id it chose. The ordering the API applies to that list was not measured. Pass the `ca_` id when you need to be sure which connection goes away.
</Callout>

<Split min={380}>
<SplitPane label="Command">

```bash
codespar connect revoke ca_7Fq2xb
```

</SplitPane>
<SplitPane label="stderr">

```text title="exit 0"
✓ Revoked connection ca_7Fq2xb.
```

</SplitPane>
</Split>

This command has no JSON path. `--json` is accepted and ignored: measured, `codespar connect revoke ca_7Fq2xb --json` exits 0 with **empty stdout** and the same success line on stderr. A script that pipes it into a JSON parser gets an empty string.

**Calls** `POST /v1/connections/{id}/revoke`, documented at [POST /v1/connections/\{id\}/revoke in the HTTP reference](/docs/api/reference/connections#post-v1connectionsidrevoke).

| When | What you get | Exit |
|---|---|---|
| A server id with no active connection for that user | `✗ No active connection for server "<arg>" and user "<user>".` | 1 |
| No argument | `error: missing required argument 'connection-or-server'` | 1 |
| Key rejected or under-scoped | `✗ POST /v1/connections/<id>/revoke → 403: <the API's own message>`, or the same shape on the lookup that precedes it | 1 |
| No API key resolved | ``✗ Not logged in. Run `codespar login` or set CODESPAR_API_KEY.`` | 1 |

## `codespar wizard [server-id]`

Answers what a provider needs before it can be connected: which are connectable, what one connection's state is, and which secrets and steps a new connection takes.

<Endpoint method="POST" path="/v1/sessions/{id}/execute" base="https://api.codespar.dev" />

| Name | Type | Required | What it does |
|---|---|---|---|
| `[server-id]` | `string` | no | Positional, and the only optional positional in the CLI. Sent as `server_id` when present |
| `--action <name>` | `string` | no | `list`, `status` or `initiate`. Validated by the CLI before anything opens. When absent the CLI sends no action at all, so the default is the backend's; the flag's help says `status` with a server-id and `list` without |
| `--country <code>` | `string` | no | Sent as `country`. The flag's help scopes it to the `list` action |
| `--environment <env>` | `string` | no | `live` or `test`, validated by the CLI. The flag's help scopes it to `initiate` |
| `--return-to <path>` | `string` | no | Sent as `return_to`. The flag's help says a dashboard path, validated `/dashboard/*` on the dashboard side |
| `-u, --user <id>` | `string` | no | The session's `user_id`. Defaults to the literal `cli-user` |
| `--json` | `boolean` | no | Root flag. Prints the whole result, including the fields the human rendering drops |

Measured wire for an initiate: `POST /v1/sessions` with `{"servers":[],"user_id":"cli-user"}`, then `POST /v1/sessions/{id}/execute` with `{"tool":"codespar_manage_connections","input":{"server_id":"asaas","action":"initiate","country":"BR","environment":"test","return_to":"/dashboard/connections"}}`, then `DELETE /v1/sessions/{id}`.

The human output has three shapes, and the shape is chosen by the `action` the **result** carries, not by the flag you passed.

<Split min={380}>
<SplitPane label="list · stdout">

```text title="codespar wizard"
SERVER      AUTH     STATUS        DIFFICULTY  CONNECTED
asaas       api_key  connected     easy        2026-09-02
stark-bank  cert     disconnected  hard        -
```

</SplitPane>
<SplitPane label="initiate · stdout and stderr">

```text title="codespar wizard asaas --action initiate"
✓ Asaas (api_key) → disconnected
server_id    asaas
difficulty   easy
connect_url  https://app.codespar.dev/connect/asaas
next_action  open_connect_url

ℹ Required secrets:
  - api_key  (starts with $aact_)

ℹ Instructions:
  1. Open the Asaas dashboard
  2. Copy the API key
  3. Paste it into the connect page

ℹ Known pitfalls:
  - The sandbox key does not work in live mode
```

</SplitPane>
</Split>

The third shape, `status`, prints a success line and a four-row block: `display_name`, `auth_type`, `difficulty`, `connected_at`. As with `discover`, the section headings go to stderr and their items to stdout, so a redirect of stdout alone gives you the secrets and the steps without the labels that introduce them. An empty list prints `(no connections)` on stderr.

**Calls** the `codespar_manage_connections` meta-tool over [POST /v1/sessions/\{id\}/execute](/docs/api/reference/sessions#post-v1sessionsidexecute). The tool itself is documented at [`codespar_manage_connections`](/docs/concepts/meta-tools/manage-connections).

| When | What you get | Exit |
|---|---|---|
| `--action` outside the three names | `✗ --action must be one of: list, status, initiate.` | 1 |
| `--environment` outside `live` and `test` | `✗ --environment must be 'live' or 'test'.` | 1 |
| No API key resolved | ``✗ Not logged in. Run `codespar login` or set CODESPAR_API_KEY.`` | 1 |
| Key rejected or under-scoped | `✗ internal error:` and a stack trace naming `createSession failed: 403 …` | 2 |
| The tool call answers `success: false` | `✗ internal error:` and a stack trace naming `connectionWizard failed: <the tool's message>` | 2 |

Both validations run before the session is opened, so a rejected flag costs no request.


## Related

- [`codespar_discover`](/docs/concepts/meta-tools/discover) and [`codespar_manage_connections`](/docs/concepts/meta-tools/manage-connections): the two meta-tools behind `discover` and `wizard`
- [Connect Links](/docs/concepts/connect-links): what `connect start` is starting
- [Servers](/docs/api/reference/servers), [Connections](/docs/api/reference/connections) and [Connect](/docs/api/reference/connect): the HTTP routes these commands call
