---
title: "Actana CLI Pairing"
url: "https://control.actana.ai/docs/pairing/cli-pairing"
description: "actana core pair on the machine being paired: which address to dial, the framed results, and an exit code per refusal."
updated: 2026-09-01T09:03:05+00:00
---

You are on the machine being paired — not on the Core. Somebody there ran [`actana pair new`](/docs/pairing/create-a-pairing-code); since 0.4.3 its handout ends in this very command with every value filled in, so usually you paste rather than type:

```bash
actana core pair NAME core.example:8443 XXXX-XXXX --session <id> --fingerprint AA:BB:…
```

`NAME` is what **this** machine will call the Core in its local registry (`actana core ls`, `actana core use`) — swap the placeholder, or it registers a Core literally called `NAME` (recoverable with `actana core rm NAME`). It is not the `--label` passed to `pair new`; that label is what the Core lists in `actana pair ls`. The address is `host:port` (or `https://` / `wss://`) — the Core's TLS port, 8443 by default. Plain `http://` and `ws://` are refused: there is no certificate on a plaintext dial, so there is no fingerprint to check.

Pass `--session <id>` from the handout — it is not optional; a bare code without it is refused. A single `<session>:<XXXX-XXXX>` ticket works too; the two ids must agree if you pass both.

## Which address to dial

The address you dial must be a name on the Core's certificate — the TLS stack verifies it before anything else happens. Since 0.4.2, `ACTANA_PUBLIC_HOST` on the Core is a **comma-separated list** and the certificate carries every name in it, so any entry verifies. Pick the one that routes from **this** machine:

| You are pairing from | Dial |
| --- | --- |
| the same host as a published Core port | `localhost:8443` |
| inside another container (Docker Desktop) | `host.docker.internal:8443` |
| inside the Core's compose network | the service name, `core:8443` |
| another machine on the network | the Core's LAN IP or DNS name |

The handout prints one command per configured address, each annotated with which endpoint the **credential** will register — that comes from the code (`pair new --public-host <addr>`), not from the address you dial. If the registered endpoint would not route from here, mint a fresh code with the right `--public-host` rather than pairing a client that then cannot reach its own Core.

## Fingerprint, or refuse

`--fingerprint` is the scriptable path, and the pasted handout command carries it. On a terminal, omit it and the CLI dials with nothing trusted, prints the fingerprint the Core presents, and asks whether it matches `pair new`. There is no skip flag. Unconfirmed — no flag, no TTY, or you answered no — refuses, and the code is **unsent**.

## What success looks like

At a terminal, a framed confirmation and the next steps:

```text
┌────────────────────────────────────────────────────────────────────────┐
│                                                                        │
│   ✓ Paired Core "NAME"                                                 │
│                                                                        │
│   Endpoint     192.168.1.50:8443                                       │
│   Sent as      laptop — the name this machine gave                     │
│   Current      "NAME" — every later verb talks to this Core            │
│   Credential   ~/.config/actana/cores/NAME.json (mode 0600)            │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

Next steps
  actana core status
    Reach the Core and report what it says. …
  actana project ls
  actana harness ls
  actana session start <project> "<prompt>"
  actana core shell
```

The `Current` row is the one to read on a machine that already had a Core: pairing a second Core does **not** move the `current` pointer, and the block then leads with `actana core use NAME`. On success the Core signs a CSR this machine generated; the private key never leaves this machine, and the issued credential is written at mode 0600 and never printed — not on `--verbose`, not in an error.

If this registry already has that `NAME`, the command **replaces** the stored credential in place — that is how you re-pair after [`token regenerate`](/docs/pairing/create-a-pairing-code#rotate-the-ca). The [Panel](/docs/pairing/panel-pairing) is the opposite: it refuses a duplicate address before spending the code.

## What failure looks like

Each class of refusal gets its own framed explanation at a terminal — what happened, and the exact command that fixes it — and its own exit code for scripts:

| Exit | Failure | The fix it prints |
| --- | --- | --- |
| 10 | Core unreachable | check the address, the port, and the route from this machine |
| 11 | endpoint answered but is not a pairable Core | check you dialled a Core's TLS port |
| 12 | no CA presented | the dial hit something that is not this Core |
| 13 | fingerprint unconfirmed | confirm interactively or pass `--fingerprint` |
| 14 | fingerprint mismatch | stop; get a current fingerprint from a fresh `pair new` |
| 15 | TLS hostname mismatch | dial a name that is on the certificate — the table above |
| 16 | certificate invalid | mint a fresh code; the material moved underneath this one |
| 17 | refused (wrong / expired / spent code) | mint a fresh code on the Core |
| 18 | rate limited | wait, then retry with a fresh code |
| 19–21 | rejected / Core error / malformed response | read the Core's log; mint a fresh code |

Down a pipe, output stays the plain lines earlier releases printed — the frame is terminal-only, and behaviour never differs by where output goes.

## See also

- [Create a Pairing Code](/docs/pairing/create-a-pairing-code)
- [How Pairing Works](/docs/pairing/how-pairing-works)
- [Pairing Troubleshooting](/docs/pairing/troubleshooting)
- [CLI Commands](/docs/cli/commands)
