---
source: https://a2a.exposed/docs/link-finish
---

# Finish a hosted secure link

Finish a hosted secure link from the terminal. The connector token and the signing secret stay out of chat and the clipboard.

You finish a hosted secure link from your terminal. The connector token and the signing secret stay out of chat, the clipboard, and the page address. You still approve in the browser. The approval page does not accept the terminal's sign-in on its own.

## Finish a link that already exists

Run `npx -y a2a-exposed@latest login` first. Link finish needs the plain account login; a scoped setup login does not authorize it.

1. On the agent's Wake page, create the secure link and wait until it is ready.
2. On your computer, run:

```bash
npx -y a2a-exposed@latest hosted link finish --agent <name>
```

3. The terminal prints a page address and a code. Open that page while you are signed in.
4. The page shows the code first, then the agent, the hostname, the full webhook URL with secrets masked, the preset, and the local address on your computer. It says when the request expires, in UTC. Approval allows getting the connector token once, and submitting the signing secret so the wake can be checked before it is saved. Finishing replaces the current wake, and the page names that wake by its URL and preset when one is saved.
5. Approve only when the code matches your terminal. For a link that already exists, approve when it is ready. Deny the request if you did not start it.
6. The terminal receives the connector token, starts the connector, and waits until the connector is connected. It then tests the wake and saves those settings.
7. Wake settings lists each active finish grant with a Revoke button. Removing the link, choosing Show command again, or signing out of every session ends the grants for that agent. If signing out cannot revoke them, the page says so and offers another try. The sign-out itself has already happened.

The approval page asks you to sign in again when your sign-in is more than 10 minutes old. That sign-in page says you return to the approval page. Approve and Deny are sent from that page only. If the request is missing, or it belongs to another account, the page shows the account you are signed in as, with a way back to your agents and a way to sign out and switch account.

A grant lasts 10 minutes. If the connector token does not arrive, run the command again. You approve once more, and the same ready link is used. Nothing is created a second time.

The terminal polls until you approve, you deny, or the request expires. Polling too often asks it to wait and try again.

## API responses and recovery

Secure links keep the preset chosen when they are created. Finishing an existing link returns that preset in `link.preset`, even before a wake has been saved. Older links without a stored preset use the agent's saved wake preset, or an empty string when none exists. The wake test must use the link's stored preset when one is set; another preset returns HTTP 400 `preset` without sending a test or spending the grant.

Token polls return HTTP 428 `authorization_pending` while waiting for approval, or HTTP 429 `slow_down` when polling too soon. Both include `interval` in seconds and the same `Retry-After` header. The interval starts at 5 seconds and increases by 5 for each early poll, up to 30 seconds. Wait for that interval before the next poll.

A temporary wake-test exception returns HTTP 503 `unavailable`, `retry_after: 5`, and `Retry-After: 5`. The grant remains usable until it expires or is revoked. Missing signing secrets and unreadable saved credentials remain input errors; follow their message before retrying.

A completed test that fails returns HTTP 200 with `{result, saved: false, message}`. `message` is the same recovery advice shown in Wake settings. Result codes are `ok`, `failed`, `tls`, `private`, `connection`, `none`, `unreadable`, `dns`, `dns-missing`, `cloudflare-target-unsupported`, `access-401`, `access-403`, `timeout`, and `origin-<status>` for other non-success HTTP statuses. Transport delivery, native and socket failures map to `connection`. DNS lookup failures remain `dns`; a hostname that does not resolve returns `dns-missing`.

The status endpoint returns HTTP 410 `expired_token` for an expired grant. Connector and wake-test return HTTP 401 `invalid_grant` for expired grants. Revoked, ended and unknown grants return HTTP 401 `invalid_grant`. Feature-disabled and unknown API actions return HTTP 404 JSON with `error: not_found` and a message. A missing request or a wrong verifier returns the same HTTP 400 `invalid_request` without identifying which value failed.

Verifiers accept the 43–128 unreserved characters specified by [RFC 7636](https://www.rfc-editor.org/rfc/rfc7636#section-4.1), including `.` and `~`. New clients use the S256 challenge: unpadded base64url of SHA-256 over the verifier's ASCII text. For compatibility, a base64url verifier encoding exactly 32 bytes also accepts a challenge hashed over those decoded bytes. Plain challenges are not accepted.

## Create refusals and recovery

On staging, a create request accepts a full hostname, including an international domain name or one trailing dot. Omit the scheme, port and path. The hostname must sit under a domain in your connected Cloudflare account; the domain itself is refused. If domains overlap, the most specific domain applies.

A create validation refusal returns HTTP 400 with `error: invalid_request`, a `field` and a `reason`. Clients use these stable codes rather than parsing the message:

| Field | Reason | Next step |
| --- | --- | --- |
| `hostname` | `hostname_format` | Supply a full hostname without a scheme, port or path. |
| `hostname` | `apex_hostname` | Choose a hostname below the domain. |
| `hostname` | `outside_zone` | Choose a hostname on a domain in the connected account. |
| `origin` | `origin_format` | Supply an http or https origin with no path or credentials. |
| `path` | `path_format` | Supply a path beginning with `/`, without a query or fragment. |
| `preset` | `preset_invalid` | Choose a supported wake preset. |

Approval-time refusals reach the token poll as HTTP 409 with `error: apex_hostname`, `outside_zone`, `existing_dns` or `existing_tunnel`. `link_exists` means the agent has a different secure link. These codes are stable for terminal clients. A provision failure stays HTTP 502 with `error: provision_failed`, `phase` and `status`; `status` is the Cloudflare HTTP status or null. Messages contain no credentials.

A stopped request cannot be approved again. The alert says to run create again in the terminal. For Cloudflare 401 or 403, the page offers Reconnect Cloudflare first. For 429 or 5xx, it says Cloudflare was busy and asks you to run create again in a few minutes. Hostname refusals ask for another hostname. Deny stops the terminal's wait.

Each agent allows five unexpired pending requests. A further request returns HTTP 429 `slow_down` before reading Cloudflare domains. Deny an earlier request or wait for it to expire, then run create again. Expired and denied requests appear as alerts; approving an expired request returns HTTP 410. The expiry shows the minutes remaining alongside UTC.
