---
title: "Execution and operation"
description: "Run one tool call from a terminal, read the schema before you do, follow the session it ran in, and tail what it logged."
---

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

Seven leaf commands run a tool and account for what it did: `execute`, `ship`, `tools show`, `sessions list`, `sessions show`, `sessions close` and `logs tail`. `sessions`, `tools` and `logs` are groups with no action of their own. Running one alone prints its own usage to stderr and exits 1.

They are not one client. `sessions *`, `tools *` and `logs tail` speak HTTP directly from the CLI. `execute` and `ship` go through the SDK, which opens a session, runs the call inside it and deletes the session in a `finally`. That seam is not cosmetic: measured, the two halves answer the same rejected key with different exit codes and disagree about what `--project` accepts. Both are below.

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`. A resolved project rides on every request as the `x-codespar-project` header; with no project resolved the header is absent.

With no key resolved, all seven refuse before opening a socket, with the same line:

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

That check runs inside the command, so an argument Commander itself rejects is reported first: `codespar execute` with no credential and no `--server` answers `error: required option '-s, --server <id>' not specified`, not the credential line.

<Callout type="warning">
**`--project` is validated on one half and not the other.** `codespar --project proj_abc sessions list` sent `x-codespar-project: proj_abc` without complaint. The same `--project proj_abc` on `execute` or `ship` exits 2 with `Error: CodeSpar projectId must match ^prj_[A-Za-z0-9]{16}$`, because the SDK constructor checks the shape and the direct-HTTP client does not. A project id of the `prj_` + 16 characters form works on both.
</Callout>

### 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` receives the data alone.

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

Two commands on this page take `--json` and ignore it, because they have no JSON path at all: `sessions close` prints its confirmation to stderr and leaves stdout empty either way, and `ship` without `--json` writes **nothing** to stdout.

### 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">
**There is no uniform exit code for "the tool call failed."** Measured against one stub answering `{"success": false, "error": "no_eligible_providers"}` to the same route: `codespar execute` printed `Tool call failed: no_eligible_providers` to stderr, printed the envelope to stdout, and exited **0**. `codespar ship`, on the identical response, exited **2** with `✗ internal error:` and a stack trace reading `Error: ship failed: no_eligible_providers`. The typed SDK wrapper behind `ship` throws a plain `Error`, which the CLI does not recognise as its own refusal type. A script that branches on the exit code of `ship` is branching on something else.
</Callout>

The same seam shows on rejected credentials. A `401` on `sessions list` is one line and exit 1; a `401` on `execute` or `ship`, raised while creating the session, is exit 2 with a stack trace naming `createSession failed: 401`.

## `codespar execute <tool>`

Opens a throwaway session against one server, sends one tool call into it, prints the result envelope, and deletes the session in a `finally`. Three requests per run: `POST /v1/sessions`, then the execute, then `DELETE /v1/sessions/{id}`.

<Endpoint method="POST" path="/v1/sessions/{id}/execute" base="https://api.codespar.dev" mayMove="Resolves `tool` as a CodeSpar meta-tool first, then as a catalog tool" />

| Name | Type | Required | What it does |
|---|---|---|---|
| `<tool>` | positional | yes | The tool name sent as `tool` in the request body |
| `-s, --server <id>` | `string` | **yes** | The session is opened with `servers: ["<id>"]`. Declared as a required option, though `--help` does not mark it |
| `-i, --input <json>` | `string` | no | Parsed as the `input` object. Mutually exclusive with `--input-file` |
| `-f, --input-file <path>` | `string` | no | Same, read from a file |
| `-u, --user <id>` | `string` | no | Sent as `user_id` when the session is created. Defaults to the literal `cli-user`, set in the command body and therefore absent from `--help` |

With neither `--input` nor `--input-file`, the input is the empty object `{}`.

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

```bash
codespar execute codespar_ship \
  --server melhor-envio \
  --input '{"action":"quote"}'
```

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

```json title="exit 0"
{
  "success": true,
  "data": {
    "id": "shp_01J",
    "status": "quoted",
    "carrier": "Correios",
    "cost_minor": 2140,
    "estimated_delivery": "2026-09-17"
  },
  "duration": 734,
  "server": "melhor-envio",
  "tool": "codespar_ship",
  "tool_call_id": "tcl_01J"
}
```

</SplitPane>
</Split>

Without `--json`, stdout carries the same envelope and stderr carries one line, `✓ codespar_ship succeeded in 734ms`. With `--json`, stderr is empty and stdout is unchanged: the envelope is already the machine-readable form.

**Calls** `POST /v1/sessions` with `{"servers": ["<--server>"], "user_id": "<--user>"}`, then [POST /v1/sessions/\{id\}/execute](/docs/api/reference/sessions#post-v1sessionsidexecute) with `{"tool": "<tool>", "input": {...}}`, then [DELETE /v1/sessions/\{id\}](/docs/api/reference/sessions#delete-v1sessionsid). All three measured on the wire. The SDK equivalent is [`session.execute`](/docs/api/sdk/session#execute).

| When | What you get | Exit |
|---|---|---|
| `--server` omitted | `error: required option '-s, --server <id>' not specified` | 1 |
| `--input` and `--input-file` both given | `✗ Pass either --input or --input-file, not both.` | 1 |
| `--input` is not an object | `✗ --input must be a JSON object.` (an array or a scalar is refused) | 1 |
| `--input` is not JSON | `✗ --input is not valid JSON: <the parser's own message>` | 1 |
| `--input-file` does not exist | `✗ internal error:` and a stack trace reading `ENOENT: no such file or directory` | 2 |
| Key rejected while opening the session | `✗ internal error:` and a stack trace reading `createSession failed: 401 <body>` | 2 |
| The tool ran and failed | `Tool call failed: <message>` on stderr, the envelope on stdout | **0** |

## `codespar ship`

Opens a session attached to no server, runs the `codespar_ship` meta-tool inside it, and closes it. The meta-tool picks the carrier, so there is no `--server`: the whole operation arrives as one JSON object under `--input`.

<Endpoint method="POST" path="/v1/sessions/{id}/execute" base="https://api.codespar.dev" mayMove="Resolves `tool` as a CodeSpar meta-tool first, then as a catalog tool" />

| Name | Type | Required | What it does |
|---|---|---|---|
| `-i, --input <json>` | `string` | one of the two | The `codespar_ship` arguments as a JSON object |
| `-f, --input-file <path>` | `string` | one of the two | The same object, read from a file |
| `-u, --user <id>` | `string` | no | Sent as `user_id` when the session is created. Defaults to `cli-user` |

The CLI checks four things before it opens the session, and nothing else. These are the fields it names, read from the command's own validator:

| Field | Required | What the CLI enforces |
|---|---|---|
| `action` | yes | One of `label`, `quote`, `track`. Anything else is refused |
| `tracking_code` | for `track` | Must be present when `action` is `track` |
| `origin`, `destination` | for `label`, `quote` | Both must be present |
| `items` | for `label`, `quote` | Must be a non-empty array |

<Callout type="warning">
That table is the CLI's check, not the tool's schema. The shapes of `origin`, `destination` and each item, the optional `service_level` and `metadata`, and every result field beyond the ones printed below are the server's contract, not this command's. Read them on [`codespar_ship`](/docs/concepts/meta-tools/ship) before building a payload.
</Callout>

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

```bash
codespar ship --input '{
  "action": "quote",
  "origin": { "postal_code": "01310100" },
  "destination": { "postal_code": "22041011" },
  "items": [{ "weight_g": 500 }]
}'
```

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

```text title="exit 0 — stdout is empty"
✓ ship shp_01J → quoted
ℹ Carrier: Correios
ℹ ETA: 2026-09-17
ℹ Cost (minor units): 2140
```

</SplitPane>
</Split>

The human form prints, when the result carries them and only then, the tracking code, the carrier, the label URL, the estimated delivery and the cost in minor units. Every one of those lines goes to **stderr**; without `--json` this command writes nothing at all to stdout. With `--json`, stdout carries the typed result object and stderr is empty.

**Calls** `POST /v1/sessions` with `{"servers": [], "user_id": "<--user>"}`, then [POST /v1/sessions/\{id\}/execute](/docs/api/reference/sessions#post-v1sessionsidexecute) with `{"tool": "codespar_ship", "input": {...}}`, then [DELETE /v1/sessions/\{id\}](/docs/api/reference/sessions#delete-v1sessionsid). The SDK equivalent is [`session.ship`](/docs/api/sdk/money#ship).

| When | What you get | Exit |
|---|---|---|
| Neither `--input` nor `--input-file` | `✗ ship requires --input '<json>' or --input-file <path>.` followed by a worked example | 1 |
| Both given | `✗ Pass either --input or --input-file, not both.` | 1 |
| `action` missing or unknown | `✗ ship.action must be one of: label, quote, track.` | 1 |
| `action: "track"` with no code | `✗ ship.tracking_code is required when action=track.` | 1 |
| `label` or `quote` without both addresses | `✗ ship.origin and ship.destination are required when action=label\|quote.` | 1 |
| `label` or `quote` with an empty `items` | `✗ ship.items must contain at least one item when action=label\|quote.` | 1 |
| The tool ran and failed | `✗ internal error:` and a stack trace reading `Error: ship failed: <message>` | **2** |

All six validations run before the session is opened, so a rejected payload costs no request.

## `codespar tools show <name>`

Prints one tool's identity and its prose description, found inside its server's tool listing.

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

| Name | Type | Required | What it does |
|---|---|---|---|
| `<name>` | positional | yes | The tool to find in that server's listing |
| `-s, --server <id>` | `string` | **yes** | The server that exposes it |
| `--json` | `boolean` | no | Root flag. Prints the tool's object verbatim |

<Callout type="info">
**Changed in 0.8.0.** `--server` is now required, and the command no longer prints input and output schemas: the per-server listing carries a name and a description. For a meta-tool's full schema use [`codespar tools meta <name>`](/docs/cli/reference/tools), which reads the published definitions without a network call.
</Callout>

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

```bash
codespar tools show codespar_ship
```

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

```text title="exit 0"
Name    codespar_ship
Server  codespar

Quote, label or track a shipment.

Input schema:
{
  "type": "object",
  "properties": {
    "action": {
      "enum": [
        "label",
        "quote",
        "track"
      ]
    }
  },
  "required": [
    "action"
  ]
}
```

</SplitPane>
</Split>

The description, the input schema and the output schema each print only when the route returns them; the `Name` and `Server` pair always prints. An `Output schema:` block follows the input one under the same rule.

**Calls** [GET /v1/servers/\{id\}/tools](/docs/api/reference/servers#get-v1serversidtools) and finds the named tool in the listing.

| When | What you get | Exit |
|---|---|---|
| `<name>` omitted | `error: missing required argument 'name'` | 1 |
| No `--server` | The refusal above, naming `codespar servers list`. Nothing is sent | 1 |
| Name not on that server | ``✗ <server> exposes no tool called "<name>". Run `codespar tools list --server <server>` …`` | 1 |
| Key rejected | `✗ GET /v1/servers/<id>/tools → 401: <the API's own message>` | 1 |

## `codespar tools list`

Lists tools across the catalog, or one server's, with each description cut to fit a terminal column. It takes `-s, --server <id>`, which is sent as the `server` query parameter, and the root `--json`. Its full block, including the truncation rule and the empty-result behaviour, is on [Catalog and discovery](/docs/cli/catalogo), because it reads the catalog rather than a running session, which is why it lives there.

## `codespar sessions list`

Lists the sessions the key can see as a six-column table, newest first as the route returns them. Both filters are forwarded to the route verbatim; the CLI parses neither and defaults neither.

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

| Name | Type | Required | What it does |
|---|---|---|---|
| `--status <s>` | `string` | no | Sent as the `status` query parameter, unparsed |
| `--limit <n>` | `string` | no | Sent as the `limit` query parameter, unparsed |
| `--json` | `boolean` | no | Root flag. Prints the array inside the envelope, not the envelope |

<Callout>
`--status` is checked against the vocabulary the listing declares — `active`, `closed`, `error` — and `--limit` must be a positive whole number. Either one outside that fails before anything is sent:

```text title="exit 1"
✗ --status expects one of active, closed, error, got "inventado".
✗ --limit expects a positive whole number, got "abc".
```

When a flag is omitted its query parameter is simply absent: `codespar sessions list` with no flags sends `GET /v1/sessions` with no query string, and the server applies its own default page size.
</Callout>

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

```bash
codespar sessions list --status active --limit 5
```

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

```text title="exit 0"
ID        USER      STATUS  SERVERS  TOOL CALLS  CREATED
ses_01J8  cli-user  active  asaas    3           2026-09-12 14:02:11
ses_01J7  agent-7   closed           12          2026-09-12 11:47:03
```

</SplitPane>
</Split>

A missing field prints `-`; `SERVERS` is the list joined with commas, and `CREATED` is the timestamp cut to `YYYY-MM-DD HH:MM:SS`. An empty result prints `(no results)` on stderr with nothing on stdout, and still exits 0; `--json` prints `[]`.

**Calls** [GET /v1/sessions](/docs/api/reference/sessions#get-v1sessions), reading the rows from a `data` array in the response.


| When | What you get | Exit |
|---|---|---|
| Key rejected | `✗ GET /v1/sessions → 401: <the API's own message>` | 1 |
| Scope rejected | `✗ GET /v1/sessions → 403: <the API's own message>` | 1 |
| No sessions match | `(no results)` on stderr, empty stdout | 0 |
| Request exceeds 30s | `✗ Request to GET /v1/sessions timed out after 30000ms.` | 1 |

## `codespar sessions show <id>`

Prints one session as a seven-line key/value block, and with `--logs` fetches that session's tool calls in a second request and prints them as a table underneath.

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

| Name | Type | Required | What it does |
|---|---|---|---|
| `<id>` | positional | yes | Percent-encoded into the path |
| `--logs` | `boolean` | no | Makes a second request for the session's tool calls and prints them |
| `--json` | `boolean` | no | Root flag. See the warning below: `--logs` changes what the JSON root is |

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

```bash
codespar sessions show ses_01J8 --logs
```

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

```text title="exit 0"
ID          ses_01J8
User        cli-user
Status      active
Servers     asaas
Tool calls  2
Created     2026-09-12T14:02:11.000Z
Closed      -

Logs:
TOOL             SERVER    STATUS   MS   AT
codespar_ship    codespar  success  812  14:02:14
codespar_charge  codespar  error    190  14:03:01
```

</SplitPane>
</Split>

`Created` and `Closed` print the raw timestamp, unlike the list, which trims it. The log table's `AT` column is the time of day only.

<Callout type="warning">
**`--logs` changes the root of the JSON.** Without it, `--json` prints the session object itself. With it, the root becomes an envelope, `{"session": {...}, "logs": [...]}`. A parser written against one form breaks on the other, and the flag that switches them is not a formatting flag.
</Callout>

**Calls** [GET /v1/sessions/\{id\}](/docs/api/reference/sessions#get-v1sessionsid), plus [GET /v1/sessions/\{id\}/tool-calls](/docs/api/reference/sessions#get-v1sessionsidtool-calls) when `--logs` is given.


| When | What you get | Exit |
|---|---|---|
| `<id>` omitted | `error: missing required argument 'id'` | 1 |
| Key rejected | `✗ GET /v1/sessions/<id> → 401: <the API's own message>` | 1 |
| Session not found | `✗ GET /v1/sessions/<id> → 404: <the API's own message>` | 1 |

## `codespar sessions close <id>`

Closes an active session by id and prints one confirmation line. It takes no flags of its own, and `--json` does nothing: stdout stays empty either way.

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

| Name | Type | Required | What it does |
|---|---|---|---|
| `<id>` | positional | yes | Percent-encoded into the path |

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

```bash
codespar sessions close ses_01J8
```

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

```text title="exit 0 — stdout is empty"
✓ Session ses_01J8 closed at 2026-09-14T16:41:02.118Z.
```

</SplitPane>
</Split>


| When | What you get | Exit |
|---|---|---|
| `<id>` omitted | `error: missing required argument 'id'` | 1 |
| Key rejected | `✗ DELETE /v1/sessions/<id> → 401: <the API's own message>` | 1 |
| Session not found | `✗ DELETE /v1/sessions/<id> → 404: <the API's own message>` | 1 |

## `codespar logs tail`

Reads the project's recent tool calls and prints one fixed-width line each, oldest first. With `--follow` it keeps polling for new ones until you interrupt it.

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

| Name | Type | Required | What it does |
|---|---|---|---|
| `--limit <n>` | `number` | no | How many recent calls to read. Default 20; a non-integer is refused before anything is sent |
| `-f, --follow` | `boolean` | no | Keep polling every 3s, printing rows not seen yet |
| `-s, --server <id>` | `string` | no | Keeps only this server — **applied to the page fetched**, not by the API |
| `--status <s>` | `string` | no | Same, and same caveat |
| `-t, --tool <name>` | `string` | no | Same, and same caveat |

<Callout type="info">
**This is a poll, not a stream.** The API has no push channel for the tool-call log, so `--follow` re-reads the most recent page every three seconds and prints the rows it has not seen.

The three filters are applied **client-side, to the page that was fetched**, because the route takes no such query parameters. Whenever one is set, the command prints how many of the fetched rows matched — so an empty result cannot be read as "nothing happened".
</Callout>
| `--json` | `boolean` | no | Root flag. One JSON document per entry (see below) |

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

```bash
codespar logs tail --status error --tool codespar_charge
```

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

```text title="exit 0"
14:02:14  SUCCESS  codespar_ship             melhor-envio      812ms
14:03:01  ERROR    codespar_charge           asaas             190ms no_eligible_providers
```

</SplitPane>
</Split>

The columns are padded to fixed widths (status to 7, tool to 24, server to 16), with the status green, red or yellow for success, error and running. `Tailing logs — press Ctrl-C to stop` goes to stderr before the first frame. A frame the parser cannot read is skipped in silence, which is how keep-alives pass through.

<Callout type="warning">
**`--json` here is not one document.** It prints a separate JSON object per entry onto the same stdout, back to back, with no array around them and no delimiter between them. A plain `| jq` over the stream will not parse it; a line-delimited or streaming reader will.
</Callout>

**Calls** [GET /v1/tool-calls](/docs/api/reference/tool-calls), once without `--follow` and once per poll with it.


| When | What you get | Exit |
|---|---|---|
| The route answers 404 | The streaming-not-available message below, naming the one-shot alternative | 1 |
| The route answers 401 or 403 | The auth message below, carrying the status it got | 1 |
| Any other status | `✗ Log stream returned <status>.` and an invitation to report it | 1 |
| The connection cannot be opened | `✗ Failed to connect: <the network error's own message>` | 1 |

The first two, verbatim:

```text title="stderr, exit 1"
✗ Auth failed (401). Check CODESPAR_API_KEY or re-run `codespar login`.
```


## Related

- [`codespar_ship`](/docs/concepts/meta-tools/ship): the schema, the carriers and the result shapes behind `codespar ship`
- [Sessions](/docs/api/reference/sessions): the HTTP routes the session commands call, and the async-settlement flow a tool call starts
- [`session.execute`](/docs/api/sdk/session#execute) and [`session.ship`](/docs/api/sdk/money#ship): the SDK calls `execute` and `ship` wrap
- [Catalog and discovery](/docs/cli/catalogo): `tools list`, and the commands that find a tool worth executing
- [Debugging](/docs/debugging): what to do with a tool call once you have its id
