---
title: "PATCH /v1/installations/{id}"
method: PATCH
path: "/v1/installations/{id}"
---

# PATCH /v1/installations/{id}

`PATCH /v1/installations/{id}`

## Request

- The document declares no server URL.
- Auth: one of:
  - HTTP bearer
  - API key in cookie `__Secure-better-auth.session_token`

Record where a deployed relay answers. Normally done by the install itself.

## Path parameters

- `id` string, required

## Request body

- ProxyUrlReport
  - `proxyBaseUrl` string, uri, required: Where the deployed proxy Worker answers, e.g. `https://send.acme.com`. An unparseable value never reaches the handler — the `url` format is validator-enforced and refuses as a 400 like any malformed body. A parseable URL that breaks the rules answers 422: it must be `https:`, it must not be the provisioning placeholder (`.invalid` hosts can never resolve), and it must carry no query, fragment or credentials — a trailing slash is dropped, and central composes `${proxyBaseUrl}/v1/send` from what remains. The same rules the engine itself enforces, so the two ends agree.

## Response `200`

The request has succeeded.

- Installation
  - `id` string, required
  - `name` string, required
  - `proxyBaseUrl` string, uri, nullable, required: Where central posts relayed sends. Absent until the proxy is deployed and the installation settles `active`.
  - `contractVersion` string, required: The transport-contract version this installation's proxy was built against. The proxy runs in the customer's own account and cannot be updated by us, so central serves every version it has ever issued. ⛔ **It is NOT how a dashboard shows which installations are behind, though this said so.** Every relay ever shipped answers `v1` and a breaking change would mint `v2` rather than move anybody, so a screen built on it reports the whole fleet current for ever. That belief is why two production relays ran three-week-old bytes unnoticed while outbound attachments corrupted. `relayStatus` is the field that answers it.
  - `relayBuild` string, nullable, required: What this installation's relay ANSWERED when the estate last asked it, or null if it never has. A hash of the relay's source. Compared against the build central currently ships to decide `relayStatus` — what the relay says it is, never what we believe we uploaded.
  - `relayStatus` 'current' | 'behind' | 'unreachable' | 'unknown', required: Whether this relay is running what central ships. `current`: it answered with the build we ship. `behind`: it answered with an older one, and needs a founder to authorise an update — central holds no standing credential for their Cloudflare account. `unreachable`: it did not answer the last sweep, which is a different problem needing a different fix. `unknown`: it has never been probed. ⚠️ **`behind` is not cosmetic.** A relay can be behind in ways that change what may be sent to it at all — the engine refuses payloads a given build would mishandle — so this is the difference between a founder's attachments working and failing.
  - `relayProbedAt` string, date-time, nullable, required: When the relay last answered, ISO-8601, or null if it never has.
  - `relayAutoUpdate` boolean, required: Whether this account asked FounderMail to keep its relay updated without a consent hop. ⛔ **Never the token itself, only whether one is held.** A credential a customer entrusted to us has no business on a read surface every screen calls.
  - `relayAutoUpdateBy` string, nullable, required: Who turned it on, and when. Null when nobody has — the default.
  - `relayAutoUpdateAt` string, date-time, nullable, required
  - `status` 'connecting' | 'active' | 'failed', required: `connecting`: the engine tenant is minted, but the proxy's URL has not been reported yet, so relayed sends have nowhere real to go — see `reportProxyUrl`, the act that settles `active`. `failed`: the connect never completed.
  - `cloudflareAccountId` string, nullable, required: The Cloudflare account this installation IS, once a founder has authorised one. Null before that, and never changed after: an account holds exactly one relay, and the id is globally unique across every organisation — two orgs connecting the same account would both deploy into it, and the second would overwrite the first's vars and signing key.
  - `cloudflareAccountName` string, nullable, required: What Cloudflare calls that account, so the console can show WHICH one was authorised.
  - `createdAt` string, date-time, required: ⛔ **The install's progress is NOT here — it is `installStatus`, and deliberately so.** It was on this model briefly. Two things were wrong with that: every row in a LIST would have to read a Durable Object to fill it in, and the field would otherwise be null in every response that is not about one specific install — a shape that lies by omission. A console polling an install already knows which one it is polling.

## Other responses

- `default`: An unexpected error response.

---

[API](https://skmtc.dev/foundermail/apis/org.md) - [All operations](https://skmtc.dev/foundermail/apis/org/llms.txt) - [OpenAPI document](https://skmtc.dev/foundermail/apis/org/revisions/8bbb36e378ab?raw)
