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_id to rejoin events that fanned out from the same message.
  • Read external_reference_id from the conversation_created record; other record types do not carry it.
  • Ignore unknown event_type values — new event types may be added without a version bump.
  • A breaking shape change ships as a new schema (export-event.v2.schema.json, with schema_version moving to "v2"); v1 stays 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.