# Envoy HTTPS API and OAuth onboarding

Connect an internal Elixir account directly over HTTPS. No Node.js, CLI installation or GitHub access is required. Use your platform's HTTP client and persistent credential store. If the platform cannot retain credentials outside task workspaces, HTTPS alone does not solve durability; use the [standing CLI guide](https://agent.envoy.money/instinct.md) with persistent storage or report the host limitation.

This is an ongoing connection. Reuse it across tasks; disconnect only when the user asks. These instructions do not grant permission to make an unsolicited purchase.

## Live endpoints

Base URL: `https://api.agent.envoy.money`
OAuth resource identifier: `https://api.agent.envoy.money/mcp` (use this exact identifier for HTTPS clients too).
Scope: `envoy`. This is the current full internal-beta mandate; separate read-only or spend-limit scopes are not implemented.

| Operation | Method and path | Body |
| --- | --- | --- |
| Authorization server metadata | GET `/.well-known/oauth-authorization-server` | None |
| Register OAuth client | POST `/register` | JSON |
| Start browser authorization | GET `/authorize` | Query parameters |
| Exchange code or refresh | POST `/token` | Form URL encoded |
| Revoke grant | POST `/revoke` | Form URL encoded |
| Start device login | POST `/device/start` | JSON |
| Poll device login | POST `/device/token` | JSON |
| Account | POST `/v1/tools/get_account` | JSON `{}` |
| Transactions | POST `/v1/tools/list_transactions` | JSON |
| Prepare or resume purchase | POST `/v1/tools/initiate_payment` | JSON |
| Report merchant outcome | POST `/v1/tools/report_checkout_outcome` | JSON |
| Disconnect current connection | POST `/v1/disconnect` | JSON `{}` |

The `/v1/` endpoints require `Authorization: Bearer <Envoy access token>` and `Content-Type: application/json`. Use tokens from secure storage, not strings embedded in prompts, command-line arguments or shared logs. Never forward Elixir tokens to the agent. Use TLS verification and do not follow redirects on token or account requests.

MCP is also live at `/mcp`; it is an alternative transport, not a prerequisite for HTTPS operations. There are no transaction webhook endpoints yet.

## Choose a login flow

Use **device login** when the host cannot receive a browser callback. Use **authorization code with PKCE** when the platform has a registered callback and persistent credential storage. Both use the user's existing Elixir phone/OTP login on `https://agent.envoy.money`. The user enters the OTP on that website, not in agent chat.

### A. Device login: no callback server needed

These are Envoy-specific device endpoints, not a claim of RFC 8628 wire compatibility.

1. Generate a cryptographically random verifier from exactly 32 random bytes, encoded base64url without padding. Compute `code_challenge = BASE64URL(SHA256(verifier))`, without padding. Keep the verifier private on the initiating host.
2. POST `/device/start` with:

```json
{"code_challenge":"<43-character SHA-256 base64url challenge>"}
```

3. The response contains `client_id`, secret `device_code`, `user_code`, `verification_uri_complete`, `expires_in` and `interval`. The current expiry is 300 seconds and initial interval is 5 seconds; obey the returned values. Show only the verification link and user code to the user, privately. Validate that the link is HTTPS on `agent.envoy.money` before presenting it.
4. Keep a background task alive and poll `/device/token` at the returned interval:

```json
{"client_id":"<returned client ID>","device_code":"<private device code>","code_verifier":"<private verifier>"}
```

5. `authorization_pending` means wait. `slow_down` means increase the interval by 5 seconds, up to 60 seconds. Stop on expiry, denial or cancellation. Do not start a second login because the user is still authenticating. Do not retry faster after a network failure.
6. Success returns OAuth tokens. Save them atomically in the host credential store before completing the task. Remove the temporary verifier and device code. Verify account access.

The browser finishing login is not a webhook to your agent. The waiting task must collect the session. If that task is killed before collection or the grant expires, start a new login.

### B. Authorization code with PKCE

1. Discover endpoints using the metadata URL. Register your actual callback; do not guess another platform's callback. POST `/register`:

```json
{"client_name":"Your agent platform","redirect_uris":["https://your-platform.example/envoy/callback"],"token_endpoint_auth_method":"none","grant_types":["authorization_code","refresh_token"],"response_types":["code"]}
```

The URL above is a placeholder: replace it with a callback your platform owns and handles. Envoy supports `none` and `client_secret_post`; confidential clients must store the returned client secret and include it in token/revocation requests. PKCE is required for both. Callback matching is exact. HTTPS callbacks and supported loopback callbacks are accepted; do not assume any custom scheme is accepted.

2. Generate a fresh verifier/challenge as above and an unpredictable `state` bound to this login attempt. Keep both privately. Open `/authorize` with these URL-encoded query parameters:

```text
response_type=code
client_id=<registered client ID>
redirect_uri=<exact registered callback>
code_challenge=<challenge>
code_challenge_method=S256
scope=envoy
resource=https://api.agent.envoy.money/mcp
state=<private unpredictable state>
```

3. When the callback receives `code` and `state`, verify state matches the initiating session and reject unexpected callbacks. Do not expose the authorization code in chat or logs. Exchange promptly; codes are short-lived and single-use.
4. POST `/token` as `application/x-www-form-urlencoded`:

```text
grant_type=authorization_code
client_id=<registered client ID>
code=<returned code>
code_verifier=<original verifier>
redirect_uri=<exact registered callback>
resource=https://api.agent.envoy.money/mcp
```

Include `client_secret` only for a client registered with `client_secret_post`.

## Store, refresh and revoke

A successful exchange returns `access_token`, `refresh_token`, `token_type: "Bearer"`, `expires_in` and scope. Save the service origin, client ID, any client secret, both tokens and calculated expiry in persistent secret storage. Tokens must not become tool output or standing instructions. Refresh before access expiry; current access tokens last one hour, but use the returned `expires_in`.

Refresh with form-encoded POST `/token`:

```text
grant_type=refresh_token
client_id=<stored client ID>
refresh_token=<stored current refresh token>
scope=envoy
resource=https://api.agent.envoy.money/mcp
```

Serialize refresh across workers and atomically replace both tokens. Refresh tokens rotate: replaying a consumed refresh token revokes its connection. Do not blindly retry the old refresh token after an ambiguous timeout; resolve the platform's stored state, or reconnect if the new token cannot be recovered. Never distribute copies of one refresh token to independent agents.

To disconnect, call authenticated POST `/v1/disconnect`, or form-encoded POST `/revoke` with `client_id`, `token=<refresh token>` and `token_type_hint=refresh_token` (plus client secret when applicable). Remove local credentials only after revocation succeeds. This does not invalidate previously disclosed reusable card details.

## Read account and transactions

POST `/v1/tools/get_account` with `{}`. POST `/v1/tools/list_transactions` with:

```json
{"page_size":5}
```

Optional history fields: `from_date`, `to_date` (YYYY-MM-DD), `page` (zero-based), `page_size` (1–100). Default range is UTC today and the preceding 29 days. Default page size is 20.

Success responses include `ok: true`, `request_id`, `checked_at` and operation fields. Account includes card status, currency, `balance_minor`, `held_minor`, `spendable_minor`, limits, channel flags and unavailable fields. Amounts are minor units: INR 29200 is ₹292. Null means unknown, not zero. Transactions are the issuer's success feed, not complete pending/decline coverage. An absent debit does not establish that an attempt failed.

## Prepare and resume a purchase

POST `/v1/tools/initiate_payment`. Supply a unique request ID, the actual final merchant context and checkout state. This example is synthetic; do not submit it as a real user purchase:

```json
{
  "request_id":"example-unique-request",
  "checkout_state":"not_submitted",
  "context":{
    "merchant":{"name":"Example merchant","url":"https://merchant.example"},
    "total":{"amount_minor":29200,"currency":"INR"},
    "purpose":"User-requested purchase",
    "observed_at":"2026-09-14T11:00:00Z",
    "source":{"kind":"browser"},
    "items":[{"name":"Example item","quantity":"1","total_amount_minor":29200}],
    "missing_data":[{"field":"totals","reason":"Merchant exposes final total without a breakdown."}]
  }
}
```

Use fresh observed time and real context. Items and totals are optional only with a reason in `missing_data` for each absent list. Source kinds are `browser`, `ucp`, `acp`, `other`; UCP/ACP need the actual `version`. Accepted complete types are published in the [client contract](https://agent.envoy.money/contracts.txt).

Response states:

- `ready`: existing card credentials, cardholder name when available, billing address currently null. This is not new-card issuance, a charge or a reservation. Do not log the response or retain credentials in purchase records.
- `funding_required`: Elixir Add money link and exact shortfall. The user funds in the app, then returns. No automatic callback or provider payment is created by Envoy. Recheck funds before proceeding.
- `checkout_review_required`: inspect merchant and account evidence before another submission.
- `already_reported`: the purchase was already reported placed; do not buy again.

For the same request/body, reuse the request ID after a safe transport retry. For changed context or resumption, keep the returned `purchase_id`, supply full refreshed context and a new request ID. After failure or uncertainty, `retry_context` must describe merchant and account checks with `conclusion: "safe_to_retry"`. This is not permission to bypass an unresolved merchant outcome.

The agent owns merchant checkout. If bank 3DS is required, use a supported user handoff and resume the same checkout. Envoy has no OTP retrieval or programmatic 3DS approval API.

## Report the merchant outcome

POST `/v1/tools/report_checkout_outcome`:

```json
{"request_id":"example-report-request","purchase_id":"<returned purchase ID>","outcome":"placed","order_reference":"<merchant-confirmed order reference>","actual_total":{"amount_minor":29200,"currency":"INR"}}
```

Outcomes are `placed`, `failed` or `unknown`. Failed/unknown require `reason`. Optional fields: `confirmation_url`, `actual_total`, `order_status_url`, `estimated_delivery` with `from_date`/`to_date`, and `tracking_url`. Do not invent optional data. A changed/enriched report needs a new request ID; an identical retry keeps its ID. Reporting an order is not issuer verification of its payment.

## Errors and acceptance

Service errors return `ok: false` and `error` containing `code`, `message` and `retry`. Respect `same_request`, `after_login`, `after_change`, `after_review` or `never`; use backoff and any `retry_after_seconds`. Validation, ownership and idempotency checks are shared across HTTPS, MCP and CLI. Do not translate upstream failure into a zero balance. Do not retry a merchant submission automatically because an Envoy HTTP call failed.

Before claiming a host integration works, complete browser login, save credentials, read account/history from a later task, test refresh serialization and test explicit disconnect. Purchase testing additionally requires user-selected context and verification of merchant and issuer evidence. This API exists; installation-free authentication and credential retention must still be verified on the actual host. Signed HTTPS tool responses are live; use the [verification instructions](https://agent.envoy.money/signatures.md). Charge/refund webhooks remain unavailable.

Read the [issuer, funds and capability information](https://agent.envoy.money/trust.md) for this beta.


## Persistent default-payment preference

Connect once, then retrieve `get_account` before selecting a payment instrument. Its `payment_preference` applies only to the authenticated connection and OAuth client. A new connection starts undecided; refreshing a token does not reset the preference.

Ask an undecided user whether Envoy should be their default for authorized purchases here. Record an explicit yes, no or later through `set_payment_preference` (`POST /v1/tools/set_payment_preference`, SDK `setPaymentPreference`, CLI `envoy preference set --input FILE`). Input example: `{"decision":"enabled","expected_revision":0,"user_confirmed":true}`. Decisions are `enabled`, `declined`, `deferred`; read the current revision from the account response. Silence is not consent. Never automatically repeat onboarding after a refusal or deferral. A changed preference requires a new explicit answer. A conflict requires reading the latest choice, not replaying an old answer.

This records agent-attested user consent, not cryptographic proof of a user interaction. It adds no purchase, transfer or funding authority. Existing beta permissions remain unchanged. Explicit payment alternatives take precedence without changing the standing default. Ask before replacing a conflicting host default. Never silently switch to a merchant-saved card when Envoy is unavailable or underfunded.

Preserve purchase ID and merchant checkout through funding and supported user bank authentication. Recheck actual funds after the app handoff. Unknown checkout means inspect merchant and account evidence before any retry. No automatic bank authentication or new recurring Envoy approval is added.

Host credential custody and routing must be tested in a fresh conversation. Server storage cannot make a host consult Envoy. Do not disconnect at task completion or claim a fresh connection inherits consent from another connection. The browser setup uses the same account and tool API; use its authenticated tool route with these exact inputs, and never place access tokens in chat.


## Shared agent payment guidance

Website login does not install a host skill. For Grok, ChatGPT and Claude, follow the [host-specific purchase-skill setup](https://agent.envoy.money/default-payments.md). Browser and MCP connections have separate scoped preferences; do not transfer consent between them.
