Data Model
Managed data that the agent creates automatically, plus the REST API for direct access.
How data works in Galadri
The agent automatically creates and maintains structured records as users interact. When someone says "I have a 2022 Honda Civic," the agent creates a vehicle record. When they book an oil change, a milestone and schedule entry appear. No schema design, no migrations, no manual writes needed.
Each end user gets their own scoped data: vehicles, documents (insurance, registration, receipts), milestones (service events, maintenance), and schedules (upcoming appointments). All of this is queryable through the REST API and manageable through the manage-data tool.
The manage-data layer works across six table families: end_users, vehicles, vehicle_groups, documents, milestones, and schedules. The sections below describe what each table is for so your own code and agents do not write the wrong kind of record.
Every conversation automatically loads the user's current vehicles, upcoming schedules, recent milestones, and documents into the agent's context. The agent just knows what the user has. This is not a retrieval step you configure. It happens automatically.
Real-time data events
When the agent creates, updates, or deletes data, your client receives a data_saved event via the SSE stream. This lets your UI update in real time without polling.
The event contains a mutations array. Each mutation includes the action that was performed, the table that was affected, and the full record after the change.
{
"type": "data_saved",
"mutations": [
{
"action": "create",
"table": "vehicles",
"record": {
"id": "a1b2c3d4-...",
"year": 2022,
"make": "Honda",
"model": "Civic",
"trim": "EX",
"odometer_km": 34500,
"created_at": "2026-03-31T14:22:00Z"
}
}
]
}Handle this event on the client to keep your UI in sync as the agent works:
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
const payload = line.slice(6);
if (payload === "[DONE]") break;
const event = JSON.parse(payload);
if (event.type === "data_saved") {
for (const mutation of event.mutations) {
switch (mutation.table) {
case "vehicles":
refreshVehicleList(mutation.record);
break;
case "schedules":
addToCalendar(mutation.record);
break;
case "milestones":
updateServiceHistory(mutation.record);
break;
}
}
}
}REST API and managed data are the same records
All data is also accessible via the REST API. The managed data layer and the REST API operate on the same records. Use the REST API for bulk operations, dashboards, and integrations outside of chat.
REST API Reference
Galadri stores structured data on behalf of your application's users. All data is scoped to your organization and a specific end user. Data can be created and managed in two ways:
- Via the AI agent — The
manage-datatool lets agents create, read, update, list, and delete records through natural conversation. - Via REST endpoints — Use the Users, Vehicles, Vehicle Groups, Documents, Milestones, Schedules, Sessions, and Session Messages endpoints for direct programmatic access.
REST endpoint matrix
| Field | Type | Description |
|---|---|---|
/v1/users | GET, POST | List end users or upsert an end-user profile by external_id. |
/v1/users/:id | GET, PATCH, DELETE | Read, update, or soft-delete one end user by Galadri UUID. |
/v1/vehicles | GET, POST | List or create vehicles for an end user. |
/v1/vehicles/:id | GET, PATCH, DELETE | Read, update, or soft-delete one vehicle by UUID. |
/v1/vehicle-groups | GET, POST | List or create named vehicle collections for an end user. |
/v1/vehicle-groups/:id | GET, PATCH, DELETE | Read, update, or soft-delete one vehicle group by UUID. |
/v1/documents | GET, POST | List or create document metadata records for an end user. |
/v1/documents/:id | GET, PATCH, DELETE | Read, update, or soft-delete one document by UUID. |
/v1/milestones | GET, POST | List or create service history and due-item records for an end user. |
/v1/milestones/:id | GET, PATCH, DELETE | Read, update, or soft-delete one milestone by UUID. |
/v1/schedules | GET, POST | List or create calendar entries, reminders, and automation schedules for an end user. |
/v1/schedules/:id | GET, PATCH, DELETE | Read, update, or soft-delete one schedule by UUID. |
/v1/sessions | GET | List chat sessions, optionally filtered by end user. |
/v1/sessions/:id | GET, PATCH, DELETE | Read a session, update hidden/unread metadata, or soft-delete it. |
/v1/sessions/:id/messages | GET | List messages for one chat session. |
Metric units
All measurements are stored in metric units (kilometers, liters, Celsius). The AI agent converts to the user's preferred units in conversation based on their profile settings.
Manage-data Table Guide
| Field | Type | Description |
|---|---|---|
end_users | profile | The end user's profile, preferences, contact info, and communication opt-ins. This is the root record for everything else. In manage-data, it is auto-scoped to the current user. |
vehicles | asset | Per-vehicle facts such as make/model/year, odometer, registration details, and lightweight estimate fields used for UI shorthand. |
vehicle_groups | organization | Named collections of vehicles like fleets, family cars, or work trucks. Use these when schedules or reminders should target multiple vehicles together. |
documents | file metadata | Metadata and extracted analysis for uploaded or linked files such as receipts, inspections, insurance cards, registrations, titles, and condition photos. |
milestones | history / due item | Service history and ownership lifecycle events. Use milestones for completed work and upcoming due items, not for generic reminders. |
schedules | calendar / automation | Appointments, reminders, recurring events, and AI-triggered outbound communications. Use schedules for things that happen at a date/time or should fire automatically. |
Communication continuity records
Galadri also maintains communication-specific records outside the manage-data tables above. These are runtime continuity tables for email, SMS, and phone workflows, not user-authored business objects.
| Field | Type | Description |
|---|---|---|
chat_sessions / messages | conversation memory | End-user chat history. Messages store organization_id, chat_session_id, and denormalized end_user_id so conversation-history retrieval, retention, and user-scoped governance can filter directly before returning bounded snippets. |
email_threads / email_messages | communication | Outbound and inbound email history, reply tracking, and the sender identity used for each thread. |
sms_threads / sms_messages | communication | Outbound and inbound SMS or MMS history for a specific user and phone-number pair. |
call_sessions / voice_threads | communication | Inbound and outbound voice-call history plus durable voice continuity threads. Callback voice threads can link to either phone-call callback objectives or SMS callback objectives when an outside business switches channels. |
voice_callback_objectives | callback continuity | Destination-scoped callback objectives for outbound phone calls placed to external contacts on behalf of a user. They track the owning Twilio credential, active-query state, status, and follow-up channel. |
comm_callback_objectives | callback continuity | Destination-scoped email and SMS callback cases for external contacts. These stay durable and auditable, track whether Galadri is actively waiting on a response, store the user follow-up channel to use when the callback resolves, and remain available in a short recent-resolved window for continuity. |
external_contact_conversations | callback continuity | Restricted external email or phone/text conversations keyed by the destination credential plus the outside sender identity. They are created only when a scoped callback candidate exists or is bound, then track the active case, recent candidates, rolling summary, and continuity state while the AI clarifies or continues work on behalf of a user. |
external_contact_conversation_events | callback audit | Append-only audit events for external-contact callback state transitions, including clarification requests and when a conversation binds to, switches between, reopens, or resolves an email, SMS, or voice callback case. |
End Users
End users are your application's users. Each end user gets their own scoped data. Users are identified by the end_user_id you provide in chat requests. If the user does not exist, one is created automatically.
/v1/usersCreate or update an end user. If a user with the same external_id already exists in your organization, it is updated (upsert).
| Field | Type | Description |
|---|---|---|
external_id | string | Your application's user ID. Used as the upsert key. |
first_name | string | User's first name. |
last_name | string | User's last name. |
email | string | Email address. |
phone | string | Phone number in E.164 format. |
user_type | "individual" | "business" | Type of user account. |
street_address | string | Street address. |
city | string | City name. |
state | string | State or province. |
zip | string | ZIP or postal code. |
country | string | Country name or ISO code. |
timezone | string | IANA timezone (e.g., "America/New_York"). |
distance_unit | "km" | "mi" | Preferred distance unit. |
volume_unit | "liters" | "gallons" | Preferred volume unit. |
currency | string | Currency code (e.g., "USD"). |
temperature_unit | "celsius" | "fahrenheit" | Preferred temperature unit. |
pressure_unit | "kpa" | "psi" | "bar" | Preferred pressure unit. |
speed_unit | "kph" | "mph" | Preferred speed unit. |
fuel_economy_unit | "l_per_100km" | "mpg" | "km_per_l" | Preferred fuel economy unit. |
marketing_opt_in | boolean | Marketing email consent. |
sms_opt_in | boolean | SMS consent. |
call_opt_in | boolean | Phone call consent. |
email_opt_in | boolean | Email communication consent. |
ready_to_buy_sell_score | number | AI-authored 0.0-1.0 score estimating buying or selling readiness. Use fractions, not percentages. |
other_archival_memory | string | Long-term profile memory that does not fit another structured field. |
/v1/usersList end users with filtering, sorting, and pagination.
Query parameters
| Parameter | Type | Description |
|---|---|---|
limit | integer | Results per page (1-100, default 20). |
offset | integer | Number of results to skip (0-based). |
sort | string | Sort field: created_at, updated_at, first_name, last_name, email, external_id. |
order | string | "asc" or "desc". |
filter.* | string | Filter by column. E.g., filter.city=Phoenix&filter.user_type=individual. Filterable: external_id, email, phone, first_name, last_name, city, state, country, user_type. |
{
"data": [
{
"id": "a1b2c3d4-...",
"external_id": "user-123",
"first_name": "Jane",
"last_name": "Smith",
"email": "jane@example.com",
"city": "Phoenix",
"state": "AZ",
"timezone": "America/Phoenix",
"distance_unit": "mi",
"created_at": "2026-03-20T10:30:00Z",
"updated_at": "2026-03-20T10:30:00Z"
}
],
"pagination": {
"total": 1,
"limit": 20,
"offset": 0,
"has_more": false
}
}Vehicles
/v1/vehiclesCreate a vehicle for an end user. The user_id field is your application's external ID for the end user (the same value you pass as end_user_id in chat requests).
| Field | Type | Description |
|---|---|---|
user_id | string (required) | Your application's external ID for the end user. |
vin | string | Vehicle Identification Number (17 characters). |
year | integer | Model year. |
make | string | Manufacturer (e.g., "Toyota"). |
model | string | Model name (e.g., "Camry"). |
trim | string | Trim level (e.g., "SE"). |
color | string | Exterior color. |
nickname | string | Display name (e.g., "Mom's car"). |
license_plate | string | License plate number. |
powertrain | "bev" | "hybrid" | "ice" | Battery electric, hybrid, or internal combustion. |
ownership_status | "owned" | "leased" | "financed" | "other" | How the vehicle is owned. |
service_bias | "cheap" | "moderate" | "premium" | How price-sensitive the owner tends to be for service decisions. |
odometer_km | number | Current odometer reading in kilometers. |
tank_capacity_liters | number | Fuel tank capacity in liters. |
ev_battery_capacity_kwh | number | EV battery capacity in kilowatt-hours. |
est_kwh_per_100km | number | Estimated energy consumption for BEVs. This is a ballpark AI-authored shorthand field, not a verified telemetry reading. |
est_liters_per_100km | number | Estimated fuel consumption for ICE or hybrid vehicles. This is a ballpark AI-authored shorthand field, not a verified measurement. |
est_maintenance_per_100km_cents | integer | Estimated maintenance cost per 100 km in USD cents. This is an AI-authored ballpark for the typical make/model/year and broad condition, used as UI shorthand rather than exact accounting. |
image_urls | string[] | URLs to vehicle photos. |
Estimated vehicle fields
Fields prefixed with est_ are intentionally approximate. They are usually written by the agent as ballpark values from the make, model, year, powertrain, and any major condition clues. They are meant for quick UI reference, not as authoritative vendor lookups, telemetry, or personalized total cost accounting.
/v1/vehiclesList vehicles for an end user. The user_id query parameter is required.
Query parameters
| Parameter | Type | Description |
|---|---|---|
user_idrequired | string | External ID of the end user. |
limit | integer | Results per page (1-100, default 20). |
offset | integer | Number of results to skip. |
sort | string | Sort field: created_at, updated_at, year, make, model, nickname. |
order | string | "asc" or "desc". |
filter.* | string | Filter by column: vin, year, make, model, trim, color, powertrain, ownership_status, nickname. |
Sessions
/v1/sessionsList chat sessions. Sessions are created automatically by the chat endpoint. Use PATCH /v1/sessions/:id to update hidden or unread, DELETE /v1/sessions/:id to soft-delete, and GET /v1/sessions/:id/messages to read messages.
Query parameters
| Parameter | Type | Description |
|---|---|---|
user_id | string | Filter to a specific end user (external ID). |
limit | integer | Results per page (1-100, default 20). |
offset | integer | Number of results to skip. |
sort | string | Sort field: created_at, updated_at, title. |
filter.hidden | boolean | Filter by hidden status. |
filter.agent_id | string | Filter by agent UUID. |
Vehicle Groups
Vehicle groups let users organize vehicles into named collections (e.g., "Fleet A", "Family cars"). Groups are managed through the manage-data tool in chat or through the Vehicle Groups REST endpoints.
| Field | Type | Description |
|---|---|---|
id | UUID | Auto-generated record ID. |
name | string | Group name. |
description | string | Optional description. |
vehicle_ids | UUID[] | Array of vehicle IDs in the group. |
Documents
Documents store metadata about files associated with vehicles or end users (receipts, insurance cards, registration papers, etc.).
| Field | Type | Description |
|---|---|---|
id | UUID | Auto-generated record ID. |
vehicle_ids | UUID[] | Associated vehicles. Documents can link to more than one vehicle. |
title | string | Document title. |
type | string | Document type: "receipt", "insurance", "registration", "title", "inspection", "warranty", "loan", "lease", or "other". |
file_url | string | URL to the stored file. |
key_info | string | Summary of key information extracted from the document. |
full_analysis | string | Detailed analysis of the document content. |
notes | string | User or agent notes. |
Milestones
Milestones track service events, maintenance, and other vehicle lifecycle events.
A milestone can begin as an upcoming due item and later be updated with completion details when the work actually happens. Agents should prefer updating a matching open milestone before creating a duplicate completion record.
| Field | Type | Description |
|---|---|---|
id | UUID | Auto-generated record ID. |
vehicle_ids | UUID[] | Associated vehicles. Milestones can link to more than one vehicle. |
document_ids | UUID[] | Related documents such as receipts, reports, photos, or inspection forms. |
type | string | Event type: "oil", "12v_battery", "tire_rotation", "tire_replacement", "registration", "inspection", "sale", "insurance_renewal", "drivers_license_renewal", or "other". |
title | string | Display title. |
completion_utc | ISO 8601 | When the milestone was completed. |
completion_km | number | Odometer at completion (km). |
completion_price_cents | integer | Cost in cents. |
next_due_utc | ISO 8601 | When the next occurrence is due. |
next_due_km | number | Odometer reading when next due. |
performed_by_name | string | Name of the service provider. |
Schedules
Schedules represent upcoming appointments, events, and automated communications. A schedule can be a simple calendar entry or an AI-triggered action that sends reminders via email, SMS, voice call, or webhook.
| Field | Type | Description |
|---|---|---|
id | UUID | Auto-generated record ID. |
vehicle_ids | UUID[] | Associated vehicles. |
vehicle_group_ids | UUID[] | Associated vehicle groups. |
title | string | Event title (required). |
description | string | Event description. |
attendees | string[] | Attendee email addresses. |
special_instructions | string | Special instructions for the appointment. |
start_time_utc | ISO 8601 with timezone offset | When the actual event occurs. Required for calendar events, optional for pure automations. |
end_time_utc | ISO 8601 with timezone offset | Event end time. |
location_name | string | Venue or business name. |
location_address | string | Full address. |
location_phone | string | Location phone number. |
location_email | string | Location email. |
recurring | boolean | Whether this event repeats. |
frequency_type | string | Recurrence frequency: "hourly", "daily", "weekly", "monthly", or "yearly". |
frequency_interval | integer | Recurrence interval (e.g., 2 for every 2 weeks). |
odometer_km | integer | Odometer threshold for an odometer-triggered schedule. |
odometer_trigger | boolean | Whether this schedule fires when a vehicle crosses odometer_km instead of at a fixed calendar time. |
agent_runtime_utc | ISO 8601 with timezone offset | When the AI agent should fire. Separate from start_time_utc. See Scheduled Communications. |
agent_runtime_prompt | string | The prompt the agent executes at agent_runtime_utc. |
agent_comm_channel | string | Delivery channel: "email", "sms", "call", or "webhook". Null means no AI delivery. |
agent_id | UUID | Which agent handles this schedule. Falls back to the org's first active agent if null. |
is_completed | boolean | Whether the event is completed. |
agent_runtime_utc vs start_time_utc
These are intentionally separate. An oil change scheduled for September 8 at 8am (start_time_utc) might trigger the AI three days earlier (agent_runtime_utc = September 5) to send a reminder. See the Scheduled Communications section for the full execution flow.
Automation-only schedules
Creating a schedule without start_time_utc is valid for pure automations. Provide agent_runtime_prompt, agent_comm_channel, and either agent_runtime_utc or an odometer trigger with odometer_km.
CRUD via Chat
Agents with the manage-data tool can create, read, update, list, and delete records in six table families. end_users is profile-scoped to the current user, while the other tables create separate records. The agent issues a tool call with the manage-data slug inside the batched actions:
{
"actions": [
{
"tool": "manage-data",
"args": {
"action": "create",
"table": "vehicles",
"data": {
"year": 2020,
"make": "Toyota",
"model": "Camry",
"trim": "SE",
"vin": "4T1BF1FK5LU123456",
"odometer_km": 72420
}
}
}
]
}Available actions:
| Field | Type | Description |
|---|---|---|
describe | action | Return the writable schema for a table before creating or updating. Use this when you need an authoritative field list. |
create | action | Create a new record. Requires table and data. |
read | action | Read a single record by ID. Requires table and id. |
list | action | List records with optional filters. Requires table. Optional: filters, limit (max 50). |
update | action | Update fields on an existing record. Requires table, id, and data. |
delete | action | Soft-delete a record. Requires table and id. |
Estimate fields are the exception
Most fields in these tables should come from the user, uploaded files, API lookups, or concrete workflow results. The main exception is the small set of vehicle fields prefixed with est_, which are intentionally allowed to be agent-authored ballpark values for UI shorthand.
The data_saved Event
When the agent creates or updates data, the stream emits a data_saved event with the full record plus mutation metadata. Use this to update your UI in real-time without polling.
// In your SSE handler
if (event.type === "data_saved") {
for (const mutation of event.mutations) {
const changedFields = mutation.changedFields || [];
if (mutation.table === "vehicles" && mutation.action === "create") {
// Add the new vehicle to your UI
addVehicleToList(mutation.record);
}
if (mutation.table === "schedules" && mutation.action === "create") {
// Show the new appointment in the calendar
addToCalendar(mutation.record);
}
if (mutation.action === "update") {
// Optionally show a compact diff card using changedFields
showDataMutationCard(mutation.record, changedFields);
}
}
}Building a data-driven UI
Combine the REST endpoints (for initial data loading and direct CRUD) with data_saved events (for real-time updates during AI conversations) to build a responsive data layer in your application.
Data Access Permissions
Each agent can be configured with a data access level that controls what the manage-data tool can do. See Data Access for details.
| Field | Type | Description |
|---|---|---|
read_write | default | Full CRUD access. The agent can create, read, update, and delete records. |
read | restricted | Read and list only. Create, update, and delete actions are blocked. |
none | restricted | No data access. The manage-data tool is not available. |
System Fields
Every record includes these auto-managed fields. You do not need to set them when creating records.
| Field | Type | Description |
|---|---|---|
id | UUID | Unique identifier. Auto-generated on create. |
organization_id | UUID | Your organization ID. Set automatically from the API key. |
end_user_id | UUID | Internal ID of the end user. Set automatically from the session context. |
created_at | ISO 8601 | Timestamp when the record was created. |
updated_at | ISO 8601 | Timestamp when the record was last updated. |
deleted_at | ISO 8601 | null | Soft delete timestamp. Null if the record is active. |