Observa help · Pairing and access

What has to be true before pairing works.

Access and pairing are two separate gates with two separate lifetimes. This page states both exactly, so you can tell a misconfiguration from a normal wait.

SHADOW_ONLY · AUTHORITY NONE · ENFORCEMENT OFF — Observa observes and reports. It has no authority to allow, deny, approve, block, retry, or change anything in your systems.

The order

Pairing happens last, and only after a person approves you

  1. Access request — you submit non-sensitive metadata. Nothing is granted.
  2. Email verification — a one-time code proves the address is yours. Still nothing granted.
  3. Human review — a person approves or denies. This step is not automatic and cannot be skipped.
  4. Invitation — approval sends a single-use invitation to your verified address.
  5. Pairing code — you create one yourself in Hosted Observa after signing in.
  6. Pair — the CLI redeems that code and stores a delivery credential.

There is no path from observa request-access straight to a paired installation, and no amount of retrying creates one. Approval also authorizes beta onboarding only: composition approval and signed-manifest issuance are separate server-side authorities.

Access request

The six states, and what each one means

StateMeaningYour next action
EMAIL_PENDINGSubmitted; the address is not yet verified. Enter the emailed code: observa request-access --code-file <0600-file>, or run request-access again for the hidden prompt.
PENDING_REVIEWEmail verified. Waiting on a human decision. Wait. observa request-status to check.
APPROVEDA reviewer approved you; an invitation was sent to the verified address. Redeem the invitation, sign in, accept the beta documents, create a pairing code, then observa pair.
DENIEDA reviewer declined. You may start over with --new.
EXPIREDA lifetime ran out, or verification attempts were exhausted. observa request-access --new --api-url <https://host>.
CANCELLEDThe request was withdrawn. You may start over with --new.

--new is accepted only from DENIED, EXPIRED or CANCELLED. From any other state it refuses ACCESS_ALREADY_PENDING.

Exact numbers

Lifetimes and limits

ThingLimitDetail
Access request in EMAIL_PENDING24 hours From submission. Not extended by anything.
Email verification code10 minutes Each code is one-time.
Resending a verification codeDoes not extend the request A new code resets the 10-minute code window only. The 24-hour request lifetime is unchanged.
Minimum gap between sends60 seconds An earlier resend refuses ACCESS_RATE_LIMITED.
Maximum sends per request3 Including the first. After that, no further code can be sent for this request.
Failed verification attemptsRetained A wrong or expired code still counts. The counter is never reset by a resend.
Attempt that ends the requestThe 5th failed attempt It moves the request to EXPIRED. Start over with --new.
Request in PENDING_REVIEW30 days From the moment the email was verified.
Beta invitation7 days, single-use Only the invited address can redeem it. When the invitation expires or is revoked, the access request becomes EXPIRED too.
Reissuing an invitation (--resend while APPROVED) Keeps the original expiry The previous invitation stops working immediately. Rate limited to 3 per hour.
New requests per email address3 per hour—
Pairing code10 minutes, single-use Bound to one organization and one workspace. A code for another tenant or workspace is refused as a boundary violation, not a typo.

These are the values the current implementation enforces. If a command behaves differently from this table, that is worth reporting — see support.

Detail

Two things about verification that surprise people

  • Your identity is frozen at submission. Name, email, organization and use case cannot be edited afterwards; attempting it refuses ACCESS_IDENTITY_IMMUTABLE. A different email address requires a new verified request.
  • One live request per address. If the same email already has a request in PENDING_REVIEW or APPROVED, verifying a second request for that address is refused.

First run. observa request-status reuses the endpoint saved in your receipt. Run before any request exists, there is nothing to reuse, so it refuses ACCESS_URL_REQUIRED rather than ACCESS_NO_REQUEST. Pass --api-url, or run observa request-access first.

The private receipt at ~/.observa/installation/observa-access/request.json is what proves the request is yours. A request id on its own authenticates nothing. Keep the receipt; if you lose it, you cannot check or continue that request.

Pairing

Initial pairing, unpair, and re-pair

Initial pairing

observa pair --base-url https://governance-plane-observa.tailb473db.ts.net --code-stdin

Paste the code, then Ctrl-D. A pairing code is never accepted as a command-line argument: --code refuses outright, so a code cannot end up in shell history or a process list. The alternative is --code-file <0600-file>, which must be owner-only.

Pairing is runtime-neutral. The code proves your invite and binds this machine to your workspace; it chooses nothing about what gets installed. The approved composition was recorded for your workspace when you were invited.

Unpair

observa unpair

This stops the service if it is running, asks Hosted to revoke the delivery credential, and reports how many credentials were revoked. It then removes the Hosted base URL, connector credential and installation binding from local config.

Hosted delivery is revoked; the installed identity is retained. The signed installation and its control-plane identity stay intact, and a recovery base URL plus the installation secret are kept locally so you can re-pair without a new code.

Re-pair

observa pair

After an unpair, pair takes no flags — passing any refuses PAIR_RECOVERY_FLAGS_REFUSED. It self-authenticates the identity you already have and asks Hosted to mint a replacement credential for the same deployment. No pairing code is redeemed and no new installation is created.

OpenClaw connector only

EXISTING_CREDENTIAL_REQUIRES_REPLACE_FLAG

This refusal belongs to the OpenClaw connector (@mcphersonai/mcpherson-governance-openclaw), not to Observa Local Node. The Local Node observa pair has no --replace-existing flag, because its unpair/re-pair path covers the same ground.

Symptom. Pairing preflight refuses before a single-use code is read or redeemed, with Pairing preflight refused: EXISTING_CREDENTIAL_REQUIRES_REPLACE_FLAG.

Cause. The selected OpenClaw profile already holds a connector credential. Re-pairing would replace it, and a silent replacement of a working credential is not something the tool will do on its own.

Inspect first, without consuming a code

observa pair --api-url <https://host> --profile default --replace-existing --preflight-only

Preflight reports the profile mode, install layout, connector version, whether the plugin is enabled, and whether a credential is present — and reads no pairing code.

Then migrate, once you have confirmed the endpoint is the one you intend

observa pair --api-url <https://host> --profile default --replace-existing

The old credential is preserved, not overwritten. Before anything is committed, the prior openclaw.json and the prior credential are written into an owner-only rollback snapshot in the connector’s own state directory:

<openclaw-profile-state>/mcpherson-governance-connector/pairing-rollbacks/pair_<uuid>/
    TRANSACTION.json
    openclaw.json.before
    deployment-credential.before

The directory is mode 0700. On success the command prints the non-secret transaction id. Restore that exact prior state with:

observa pair --profile default --rollback pair_<uuid>

Rollback refuses if the profile changed since pairing, rather than restoring over an unknown state. If activation verification fails during pairing, the previous config and credential are restored automatically.

Failure states

Why a pairing attempt gets refused

RefusalWhat it meansSafe action
PAIR_REFUSED Hosted declined the code: unknown, already used, expired, or minted for a different tenant or workspace. These are deliberately one answer, so the refusal is not an oracle. Create a fresh code and retry once. If a fresh code also fails, stop and contact support — do not loop.
PAIR_HOSTED_UNREACHABLE The connection failed or timed out (10s). Nothing was redeemed. Check the URL and your network, then retry. The code is not consumed by a failed connection.
PAIR_BASE_URL_FLAG_REQUIRED / PAIR_BASE_URL_REFUSED --base-url is missing or is not https://host[:port] (plain http is accepted only on loopback). Supply the origin only — no path, query or fragment.
PAIR_CODE_INPUT_REQUIRED Neither or both of --code-stdin and --code-file were given. Choose exactly one.
PAIR_CODE_ARGV_REFUSED You passed the code as an argument. Use --code-stdin or an owner-only --code-file. Treat that code as exposed and mint a new one.
PAIR_CODE_FILE_PERMISSIONS_REFUSED The code file is group- or world-readable. chmod 600 the file.
PAIR_RECOVERY_FLAGS_REFUSED You passed flags to pair while a re-pair is pending. Run observa pair with no flags.
UNPAIR_ACTIVE_BINDING_REQUIRED There is no complete, self-consistent active pairing to unpair. Run observa diagnose. Do not hand-edit the config file.
PAIR_CREDENTIAL_SHAPE_REFUSED / PAIR_CREDENTIAL_METADATA_DIVERGED / PAIR_RESPONSE_MALFORMED The response did not match what the CLI independently derived. Nothing was stored. Do not retry blindly. Contact support with the refusal code.