# HTTP

HTTP is a request-and-response protocol: a client sends a method, a path and headers, the server answers with a status code and usually a body, and the server keeps no memory of the exchange afterwards. Every Connect surface — the app, the public API, the MCP endpoint, inbound provider webhooks — is HTTP. Reading its status codes correctly is most of the work of debugging an integration.

- **Status:** Reference
- **Audience:** developer
- **Last verified:** 2026-09-10
- **Canonical:** https://connectbyjbrh.com/docs/technology/http/

## The parts that matter

```http
GET /api/health HTTP/1.1
Host: connectbyjbrh.com
Accept: application/json

HTTP/1.1 200 OK
Content-Type: application/json
```

The method states intent, and the intent carries a promise. `GET` and `HEAD` must not change anything; `PUT` and `DELETE` must be safe to repeat with the same result; `POST` promises nothing at all, which is why every reliability mechanism in this reference exists mostly to make repeated `POST`s survivable.

Statelessness is the property people forget. The server does not remember the previous request, so anything that must persist between two of them travels in a cookie, a token or a record. That is why a session is a separate topic, and why "it worked a second ago" is never on its own an explanation.

## Codes that mean what they say

| Code | What it means here | What to do |
|---|---|---|
| 200 | The request was handled. The body may still describe a refusal. | Read the body. A refusal is a decision, and Connect records it as one. |
| 401 | No usable session or key. | Authenticate. Do not retry the same request unchanged. |
| 403 | Authenticated, and this path is not permitted for this session. | Stop. On a customer session this is usually the `customer_safe` allowlist, not a bug in the feature. |
| 404 | No such path, or no such record *for you*. | Under row-level security, a record in another workspace is invisible rather than forbidden — 404 is the correct and deliberate answer. |
| 405 | The method is wrong for this path. | On the MCP endpoint this is expected: the 2026-07-28 revision is POST-only and answers GET or DELETE with 405. |
| 429 | Too many requests. | Back off. Slow the queue, do not widen it — see the rate-limiting page. |
| 5xx | The server failed. | Retry with backoff, but only if the request is safe to repeat. |

## Codes that mislead

**A 200 that is a failure** — An action can be refused, held for approval, or blocked by a suppression entry and still return 200, because the request itself succeeded. Treat the body as the result and the status as the transport.
**A 403 that is not about permission** — A customer session calling an Owner path is rewritten by `tenantAdapt`, and anything it does not recognise becomes `/api/customer/ui/blocked` and a 403. The feature is not broken and the account is not restricted; the path simply has no customer form yet.
**A 404 that means invisible** — Workspace isolation is enforced three times over — the allowlist, the ORM kernel, and PostgreSQL row-level security. A row you cannot see returns nothing, which is indistinguishable from it not existing. That is the intended behaviour, not a lookup failure.

> **Note** The application's own generated description at `/openapi.json`, and its `/docs` and `/redoc` viewers, are closed and stay closed. The public description is a separate, hand-maintained OpenAPI 3.1.0 document. Probing the closed ones tells you nothing about the API.

## Does Connect use HTTP?

**Used, everywhere.** It is the only transport for the browser app, the public API, the MCP server, the A2A agent card, the machine-readable manifests and every inbound provider webhook. The one thing that is not plain request-and-response is the app's own event stream, which is server-sent events — still HTTP, but one long-lived response rather than many short ones.

`/api/health` is the health endpoint. There is no `/healthz`, no `/health` and no `/readyz`; a monitor pointed at one of those is checking nothing and will report green forever, or red forever, depending on how your prober treats a 404.

## Questions

### Should a client treat every non-200 the same way?

No, and doing so is how integrations end up hammering a server that already told them to stop. 401 needs new credentials, 403 needs a different path or no path at all, 404 may be correct, 429 needs a slower queue and 5xx needs backoff. Only the last two are retryable without changing the request.

### Why does a request that clearly did something return 200 with a refusal?

Because Connect distinguishes between the request failing and the action being declined. A refusal is recorded in the audit trail with the rule that produced it, which is more useful than a bare 4xx — and it means a client must read bodies rather than only counting statuses.

### Is HTTP/2 or HTTP/3 different for an integrator?

Not semantically. Methods, statuses and headers mean the same things; the framing and multiplexing underneath change. Nothing in this documentation depends on the wire version.

## Related

- [REST](https://connectbyjbrh.com/docs/technology/rest/)
- [HTTPS and TLS](https://connectbyjbrh.com/docs/technology/https/)
- [Rate limiting](https://connectbyjbrh.com/docs/technology/rate-limit/)
- [Server-sent events](https://connectbyjbrh.com/docs/technology/sse/)
- [API error shapes](https://connectbyjbrh.com/developers/api-errors/)
- [A tenant facade that fails closed](https://connectbyjbrh.com/research/closed-by-default-facade/)

## What this page is based on

- Connect source pack (docs-source/sources/GENERAL.md §2, §10)
- Connect capability registry (docs-source/facts.py) — PROTOCOLS.mcp, PRODUCTION
- https://www.rfc-editor.org/rfc/rfc9110 — HTTP Semantics
