Required scope
distill:writeUsedistill:read to inspect sources and captured-call metadata.Secret boundary
Human → APIThe upstream key is write-only and never belongs in Pilot context.Join key
External IDCarry your request or ticket ID through capture, feedback, and diagnosis.Owner and boundary
A source tells Lizzy where teacher requests should go and which calls may be retained. Your server sends traffic through the OpenAI-compatible proxy. Lizzy forwards the request, returns the provider response, and records non-body metadata even when body sampling is off.
Source creation is a human-controlled operation because it containsupstream_key. Pilot can inspect an existing source and explain its state, but policy forbids it from reading captured bodies or creating and updating credentials.
Configure a source
Create the source with the same live or test token that will call the proxy. The optional loop groups provenance, while name becomes the stable value used byX-Lizzy-Source. Capture resources are mode-isolated even though the loop itself spans modes.
curl -sS -X POST "https://lizzy.albinilabs.com/v1/distill/sources" \
-H "Authorization: Bearer $LIZZY_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: source-production-teacher-v1" \
--data '{
"loop": "dlp_REPLACE_ME",
"name": "production-teacher",
"base_url": "https://api.openai.com/v1",
"upstream_key": "<upstream-provider-key>",
"default_capture": true,
"capture_sample_rate": 1,
"redaction": {
"mode": "patterns",
"patterns": ["email", "ssn"],
"drop_fields": ["metadata.email", "messages[].metadata.ssn"]
}
}'
Persist the returned id. The response never echoes the upstream key. Pattern redaction recognizes email, phone, credit_card,ssn, and ip_address. A drop path uses dot-separated object keys and [] for array traversal, as in messages[].metadata.ssn. Unknown redaction fields are rejected as unknown_parameter; they never silently switch redaction off.
{
"id": "dsc_01K...",
"object": "distill_source",
"loop": "dlp_01K...",
"name": "production-teacher",
"base_url": "https://api.openai.com/v1",
"default_capture": true,
"capture_sample_rate": 1,
"status": "active",
"livemode": true,
"captured_count": 0
}
Proxy an OpenAI-compatible request
Change only the client base URL and credential. Keep the upstream model name in the body.X-Lizzy-External-Id is the join key back to your system; tags are comma-separated labels for later filtering. The response body remains OpenAI-compatible.
curl -sS "https://lizzy.albinilabs.com/v1/proxy/chat/completions" \
-H "Authorization: Bearer $LIZZY_API_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Lizzy-Source: production-teacher" \
-H "X-Lizzy-Tags: support,refund" \
-H "X-Lizzy-External-Id: ticket-123" \
--data '{"model":"gpt-4.1-mini","messages":[{"role":"user","content":"Can I change my delivery address?"}]}'
Label the outcome and verify capture
- 1
List metadata, not bodies
Call
GET /v1/distill/calls?external_id=ticket-123. Lists expose model, tokens, latency, tags, source, feedback, and body status without returning prompts or answers. - 2
Attach the product outcome
Post feedback with exactly one of
callorexternal_id. A score is a number from -1 to 1;thumbs_upandthumbs_downare also accepted. - 3
Use the same labels in data filters
Source IDs, tags, served-by values, and feedback scores let a later dataset describe why each row belongs in training instead of relying on an opaque export.
curl -sS -X POST "https://lizzy.albinilabs.com/v1/distill/feedback" \
-H "Authorization: Bearer $LIZZY_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"external_id":"ticket-123","score":1,"comment":"Address updated","tags":["resolved"]}'
Posting feedback again updates the current opinion on the newest matching external ID. It is not an append-only event log, so keep your own history if the sequence matters.
Redaction and retention are observable states
- 1stored
The body was sampled, redacted according to policy, and remains available to an authorized human.
- 2sampled_out
Metadata exists, but the body was intentionally never stored.
- 3redacted
A deletion request purged the body while preserving metrics and provenance.
- 4expired
Retention removed the body; the metadata row remains for audit and aggregate use.
GET /calls/{call_id}/body distinguishes an unknown call from a body that never existed and one that was removed. DELETE /calls/{call_id} is a body purge, not a wholesale removal of metering or provenance.
Diagnose capture without retry loops
/v1/distill/calls?external_id={your_id}The safest first diagnostic: it proves whether the call reached capture and shows source, served-by, body status, latency, and feedback metadata.