With the default off, no prompt text leaves the machine.
Captured request and response bodies are the highest-leak data the daemon
holds: a request body carries the full prompt you sent, a response body
carries the model's full completion. Cloud can store them, but only when
the workspace has explicitly turned that on. The switch is
store_prompt_content, and it defaults to OFF. When it is off the
ordinary metadata sync carries no prompt or completion text at all. This
page documents the mechanism end to end: the wire shape, the auth, the
opt-in gate, and what the default protects.
1. What syncs when bodies are off
The default metadata sync (POST /v1/requests/batch) never carries prompt
or completion text. With store_prompt_content off, the only thing that
leaves the machine per request is metadata:
- Token counts (prompt, completion, cache-read, cache-write).
- Model and provider.
- Cost (recomputed cloud-side from seeded pricing rates).
- Latency and HTTP status / error class.
- Project attribution and
prompt_hash(a SHA-256 fingerprint, not the prompt).
That list is structurally complete: with the gate off, the body endpoint persists nothing, so there is no path by which prompt text reaches the cloud database. See Data sync & retention for the full per-field table.
Plain-English placeholder: when body storage is off, we do not store the text of your prompts or the model's replies, only the metadata listed above. (Exact lawful-basis wording under UK GDPR Art.5(1)(c)/25 is pending legal review.)
2. The body endpoint
Bodies sync on a separate endpoint from the metadata batch, live since daemon v0.2.2:
POST /v1/requests/{id}/body
Authorization: Bearer hm_sync_…
The token must carry the sync or admin scope. The wire shape is
BodyEnvelopeV021. The daemon sends two POSTs per captured request,
one with direction=request and one with direction=response. Both upsert
into a single request_bodies row keyed on request_id. It is
last-write-wins per direction: there is no content dedup, and a re-send of
the same direction overwrites the prior value.
The envelope fields:
| Field | What it is |
|---|---|
request_id | UUID of the captured request the body belongs to |
direction | request or response |
content_type | MIME type of the body (e.g. application/json) |
body_b64 | Base64 of the post-redaction body bytes |
redaction_applied | Boolean: did any redaction rule fire |
redaction_summary | List of rule names that fired, e.g. ["email", "aws-access-key"] |
original_size_bytes | Size of the body before base64 encoding |
Redaction happens daemon-side, before upload. The bytes in body_b64
are already redacted; the cloud never sees the pre-redaction body, and
redaction_summary reports which rules ran so the result is auditable.
Cloud decodes body_b64 from base64 to UTF-8 and stores the text. Input
that is not valid base64, or that does not decode to UTF-8, is rejected
with a 400.
3. The opt-in gate
Persistence is gated on the workspace flag store_prompt_content, which
defaults to FALSE. The endpoint's behaviour depends entirely on it:
- Off (default). The endpoint returns
200with{stored: false, reason: "store_prompt_content_disabled"}and persists nothing. The body is discarded and unrecoverable. - On. The endpoint persists the decoded body and returns
204.
Because the default is off, the privacy promise holds cloud-side, not just by daemon convention: the database has no row to read because the endpoint refused to write one.
When redaction_summary is non-empty the cloud OR-merges redaction_applied
across the two directions, so a row reads as redacted if either the request
or the response had a rule fire.
4. Daemon-side body sync
On the daemon, body sync is its own switch, off by default and independent
of the cloud-side store_prompt_content flag. Inspect the current policy:
$ halton-meter cloud privacy show
Enabling daemon body upload is documented in
Data sync & retention. Both gates must be
on for a body to be stored: the daemon must be configured to upload it, and
the workspace must have store_prompt_content on. If the daemon uploads
while the workspace gate is off, the endpoint returns 200 and discards the
payload.
$ halton-meter cloud sync ● POST /v1/requests/<id>/body → 200 {stored: false, reason: store_prompt_content_disabled} # Gate off: the cloud discarded the body. Metadata still synced.
Plain-English placeholder: who on a team may view a stored body, and the audit logging around that access, is governed by a separate policy. (Exact teammate prompt-visibility wording is pending legal review; the live access rule is a role check, documented under Consent.)
See also
- Data sync & retention: every field that syncs, body sync switch, retention windows
- Consent: checkout consent and teammate prompt-view access
- SQLite schema: the local shape Cloud reads a subset of
- Cloud overview: what the hosted cloud adds
- Connect your meter: pair the daemon first