Conversation data export
Freeday can export conversation data to your storage bucket as it happens: conversation starts, messages, CSAT ratings, classification labels, and skill executions. Use it to feed your data warehouse, BI tooling, or quality monitoring without calling the API.
File format
Exports are written as NDJSON: one JSON object per line, one line per
event. Validate each line against the JSON Schema (Draft 2020-12) — browse the
fields in the Data export event schema
reference. Every field carries a description, so validators and code
generators surface field-level documentation automatically.
Event types
A line's event_type selects which shape applies (a oneOf discriminated
union in the schema):
event_type |
extra fields |
|---|---|
conversation_created |
external_reference_id |
message_sent |
role, text, provenance (nullable) |
csat_received |
rating (1–5), feedback (nullable), rating_source |
message_classified |
classification_label |
skill_executed |
name, end_state |
Every event also carries the shared envelope: schema_version ("v1"),
event_id, company_id, digital_employee_id, conversation_id,
message_id, occurred_at.
external_reference_id is the conversation's identifier in the system it came
from — a ticket number, an email id — so you can line exported records up
against your own records. It is null when the conversation did not come from
such a system. Only the conversation_created record carries it: join a
conversation's other records to that one on conversation_id.
You receive one conversation_created record per conversation. A user can
re-start a conversation on some channels, which repeats the record with the
same event_id, so the deduplication below collapses it back to one.
AI-generated replies
A message_sent record from a digital employee carries provenance: a link to
a signed record in the C2PA Content Credentials standard that marks the reply as
AI-generated. The record's text is exactly the text that record signs, so you
can verify an exported reply as it is, with no need to rebuild the text. See
Verifying AI-generated replies for
the Freeday root certificate and how to read the result.
provenance is null on every other message.
With the official C2PA Python SDK (pip install c2pa-python), verify one
exported record like this:
import io
import json
import urllib.request
import c2pa
def verify_exported_reply(record: dict, root_certificate_pem: str) -> dict:
with urllib.request.urlopen(record["provenance"]["manifest_url"]) as response:
manifest = response.read()
settings = c2pa.Settings.from_dict(
{
"verify": {"verify_trust": True},
"trust": {"trust_anchors": root_certificate_pem},
}
)
with c2pa.Reader(
"text/plain",
io.BytesIO(record["text"].encode("utf-8")),
manifest_data=manifest,
context=c2pa.Context(settings=settings),
) as reader:
return json.loads(reader.json())
Freeday signs the record a few seconds after the reply is sent. If you verify a
record within a minute of receiving it and the download returns 404, wait a
few seconds and try again.
Example records
One line per record, exactly as written to the bucket (formatting added here for readability — in the file each object is on a single line):
{"schema_version":"v1","event_type":"conversation_created","event_id":"39d60872-…:created","company_id":"8e93b6ae-…","digital_employee_id":"9ec14f36-…","conversation_id":"39d60872-…","message_id":"0c81…","occurred_at":"2026-07-02T16:22:58.104221Z","external_reference_id":"12345"}
{"schema_version":"v1","event_type":"message_sent","event_id":"44f2…:message","company_id":"8e93b6ae-…","digital_employee_id":"9ec14f36-…","conversation_id":"39d60872-…","message_id":"44f2…","occurred_at":"2026-07-02T16:23:01.488680Z","external_reference_id":null,"role":"digital_employee","text":"Hoi! Kan ik je ergens mee helpen?","provenance":{"manifest_id":"0b6f2d4e-…","manifest_url":"https://api.freeday.ai/analytics/v1/public/provenance/0b6f2d4e-…","ai_origin":"ai_generated"}}
{"schema_version":"v1","event_type":"skill_executed","event_id":"44f2…:skill:4465…","company_id":"8e93b6ae-…","digital_employee_id":"9ec14f36-…","conversation_id":"39d60872-…","message_id":"44f2…","occurred_at":"2026-07-02T16:23:01.488680Z","external_reference_id":null,"name":"Small talk","end_state":"success"}
{"schema_version":"v1","event_type":"message_classified","event_id":"1979…:classified:1199…","company_id":"8e93b6ae-…","digital_employee_id":"9ec14f36-…","conversation_id":"1cc07a85-…","message_id":"1979…","occurred_at":"2026-07-02T18:09:45.034173Z","external_reference_id":"12345","classification_label":"freeday"}
{"schema_version":"v1","event_type":"csat_received","event_id":"fd36…:csat:48d7…","company_id":"38dc728d-…","digital_employee_id":"9137d8b7-…","conversation_id":"fe11c421-…","message_id":"fd36…","occurred_at":"2026-07-02T19:54:34.175924Z","external_reference_id":null,"rating":5,"feedback":"Friendly and quick.","rating_source":"ai"}
event_id embeds the message_id plus the record kind (e.g. …:message,
…:skill:<uuid>) — that keeps it unique per record while message_id stays
shared across the events of one message. conversation_created is the
exception: it is keyed on the conversation_id, so a re-start repeats an id you
have already seen.
Integration guidelines
- Delivery is at-least-once — deduplicate on
event_id. - Use
message_idto rejoin events that fanned out from the same message. -
Read
external_reference_idfrom theconversation_createdrecord; other record types do not carry it. -
Ignore unknown
event_typevalues — new event types may be added without a version bump. -
A breaking shape change ships as a new schema (
export-event.v2.schema.json, withschema_versionmoving to"v2");v1stays stable.
Naming your digital employees
digital_employee_id says which digital employee handled a conversation. It is
a stable UUID rather than a name, so a digital employee can be renamed without
breaking data you have already stored. The portal lists each digital employee's
name next to its id on the automated export settings page, so you can resolve
them in your own reporting.
Contact the Freeday team to enable the export for your environment.