Skip to main content

Data in Gravity Rail

Gravity Rail provides a flexible data management system that lets you define custom data structures, store records, and control access through a permissions system.

Core Concepts

Data Types

A Data Type is a custom schema you define for your data. Think of it like a database table or spreadsheet template:

{
"id": 123,
"name": "Patient",
"slug": "patient",
"description": "Medical patient records",
"isCollection": false,
"fields": [
{
"id": 1,
"name": "First Name",
"slug": "first_name",
"fieldType": "text",
"required": true,
"shouldIndex": true
},
{
"id": 2,
"name": "Medical ID",
"slug": "medical_id",
"fieldType": "text",
"required": false,
"shouldIndex": true,
"isExternalId": true
},
{
"id": 3,
"name": "Admission Date",
"slug": "admission_date",
"fieldType": "date",
"required": false,
"shouldIndex": false
}
]
}

Key properties:

  • slug: A unique identifier for the data type (used in APIs)
  • isCollection: If true, members can have multiple records of this type. If false, at most one
  • fields: Array of field definitions (see below)

Data Fields

A Data Field defines a single piece of data within a Data Type:

{
"id": 1,
"name": "First Name",
"slug": "first_name",
"fieldType": "text",
"required": true,
"shouldIndex": true,
"isAdminOnly": false
}

Field properties:

  • slug: Unique identifier within the data type
  • fieldType: The data type (see Field Types below)
  • required: If true, this field must have a value
  • shouldIndex: If true, this field is indexed for fast searching and filtering
  • isAdminOnly: If true, only workspace managers can view/edit this field

Data Records

A Data Record is an instance of a Data Type containing actual data:

{
"id": 789,
"dataTypeId": 123,
"memberId": 42,
"externalId": "pat_456",
"fieldValues": {
"first_name": "Alice",
"medical_id": "MED123456",
"admission_date": "2024-01-15"
},
"createdAt": "2024-01-15T10:00:00Z",
"updatedAt": "2024-01-20T15:30:00Z"
}

Key properties:

  • externalId: A unique identifier (enforced across all records of this type). Useful for matching records to your external system
  • fieldValues: Object containing the actual field data
  • memberId: The member who owns/created this record

External IDs

The External ID field provides a unique identifier across all records of a data type. This is critical for reconciliation with external systems.

Behavior:

  • External ID values must be unique across all members for that data type
  • If you try to create/update a record with a duplicate external ID, you get an error
  • For non-collection data types, you can update the external ID of an existing record
  • External IDs are indexed and searchable

Example use case:

You sync patient data from an external EMR system. Each patient has an EMR ID (e.g., "EMR-789"). You set the Medical ID field as the external ID:

# Creating a patient record with external ID
curl -X POST https://api.gravityrail.com/api/v2/w/{workspace_uuid}/data-types/123/records \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fieldValues": {
"first_name": "Alice",
"medical_id": "EMR-789"
},
"externalId": "EMR-789"
}'

# Later, you can look up by external ID
curl "https://api.gravityrail.com/api/v2/w/{workspace_uuid}/data-types/123/records?externalId=EMR-789" \
-H "Authorization: Bearer YOUR_API_KEY"

Field Types

Gravity Rail supports the following field types:

Field TypeDescriptionExample
textShort text (strings)"John Doe", "Support Agent"
textareaLong text (paragraphs)Multi-line notes
emailEmail address"alice@example.com"
phonePhone number"+1-555-123-4567"
numberInteger or decimal42, 3.14
dateDate (YYYY-MM-DD)"2024-01-15"
datetimeDate + time (ISO 8601)"2024-01-15T10:30:00Z"
checkboxBoolean (true/false)true, false
selectSingle selection from predefined options"active", "inactive"
multiselectMultiple selections["red", "blue", "green"]
linkURL"https://example.com"
jsonArbitrary JSON data{"nested": "object"}

Permissions & Access Control

Member Access

Members have different access levels depending on their role:

graph TB
M["Member with Role"]
P["Permission Service"]
DT["Data Type"]
DR["Data Record"]

M -->|has| P
P -->|controls access to| DT
DT -->|contains| DR
M -->|can only modify| OWN["Own Records<br/>(memberId == auth.memberId)"]
MGR["Workspace Manager"] -->|can modify| ANY["Any Record<br/>(manages workspace)"]

style M fill:#e8f5e9
style OWN fill:#ffebee
style ANY fill:#fff3e0

Default behavior:

  1. Regular Members: Can view and edit only their own records
  2. Workspace Managers: Can view and edit any record
  3. Admin-only fields: Only workspace managers can view/edit these fields

Role-Based Data Access

Member roles control which data types they can access:

# Create a "Support Agent" role with limited data access
curl -X POST https://api.gravityrail.com/api/v2/w/{workspace_uuid}/member-roles \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Support Agent",
"permissions": [
"chats:read",
"chats:write",
"data:read:patient", # Can READ patient data
"data:write:patient", # Can WRITE to patient data
"data:read:internal" # Can READ internal data (but not write)
]
}'

Permission format: {action}:{scope}:{target}

  • action: read, write, delete, admin
  • scope: Usually a data type slug or feature (e.g., patient, internal, chat)
  • target: Optional specificity

Creating Data Types

Via API

curl -X POST https://api.gravityrail.com/api/v2/w/{workspace_uuid}/data-types \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Patient",
"slug": "patient",
"description": "Medical patient records",
"isCollection": false,
"fields": [
{
"name": "First Name",
"slug": "first_name",
"fieldType": "text",
"required": true,
"shouldIndex": true
},
{
"name": "Medical ID",
"slug": "medical_id",
"fieldType": "text",
"required": false,
"shouldIndex": true
},
{
"name": "Email",
"slug": "email",
"fieldType": "email",
"required": false,
"shouldIndex": true
},
{
"name": "Admission Date",
"slug": "admission_date",
"fieldType": "date",
"required": false,
"shouldIndex": false
},
{
"name": "Internal Notes",
"slug": "internal_notes",
"fieldType": "textarea",
"required": false,
"isAdminOnly": true
}
]
}'

Via UI

  1. Navigate to your workspace's "Data" section
  2. Click "Create Data Type"
  3. Enter name, slug, and description
  4. Add fields (choose field type, set required/indexed)
  5. Save

Working with Data Records

Create a Record

curl -X POST https://api.gravityrail.com/api/v2/w/{workspace_uuid}/data-types/{data_type_id}/records \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fieldValues": {
"first_name": "Alice",
"medical_id": "MED123456",
"email": "alice@example.com",
"admission_date": "2024-01-15"
},
"externalId": "MED123456"
}'

Update a Record

curl -X PUT https://api.gravityrail.com/api/v2/w/{workspace_uuid}/data-types/{data_type_id}/records/{record_id} \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fieldValues": {
"first_name": "Alice Johnson",
"admission_date": "2024-01-20"
}
}'

Query Records

# Find records by field value
curl "https://api.gravityrail.com/api/v2/w/{workspace_uuid}/data-types/123/records?first_name=Alice" \
-H "Authorization: Bearer YOUR_API_KEY"

# Find by external ID
curl "https://api.gravityrail.com/api/v2/w/{workspace_uuid}/data-types/123/records?externalId=MED123456" \
-H "Authorization: Bearer YOUR_API_KEY"

# Pagination
curl "https://api.gravityrail.com/api/v2/w/{workspace_uuid}/data-types/123/records?page=1&pageSize=50" \
-H "Authorization: Bearer YOUR_API_KEY"

Delete a Record

curl -X DELETE https://api.gravityrail.com/api/v2/w/{workspace_uuid}/data-types/{data_type_id}/records/{record_id} \
-H "Authorization: Bearer YOUR_API_KEY"

Files and Folders

Gravity Rail includes a virtual filesystem for storing reference documents and providing context to AI agents.

Understanding Files & Folders

  • Files: Any document (PDF, images, text, etc.) you want to store
  • Folders: Organize files hierarchically
  • AI Context: Files are automatically provided as context to AI agents, enabling RAG (Retrieval-Augmented Generation)

Create a Folder

curl -X POST https://api.gravityrail.com/api/v2/w/{workspace_uuid}/folders \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Knowledge Base",
"slug": "knowledge-base",
"description": "Reference documents for support team"
}'

Upload a File

curl -X POST https://api.gravityrail.com/api/v2/w/{workspace_uuid}/files \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "file=@product-guide.pdf" \
-F "folderId=1" \
-F "name=Product Guide" \
-F "description=User guide for our product"

List Files in a Folder

curl "https://api.gravityrail.com/api/v2/w/{workspace_uuid}/folders/1/files" \
-H "Authorization: Bearer YOUR_API_KEY"

Delete a File

curl -X DELETE https://api.gravityrail.com/api/v2/w/{workspace_uuid}/files/{file_id} \
-H "Authorization: Bearer YOUR_API_KEY"

Accessing Data in Expressions and Templates

Beyond the REST API, you can access member data in CEL expressions (for conditions and routing) and Jinja2 templates (for messages and notifications).

Core member fields are available directly, including member.name, member.email, member.phone, member.date_of_birth, and member.age.

Single-Record Data Types

Access fields directly via member.data:

member.data.patient.first_name
member.data.contact.email

Collection Data Types

Collection data types (where isCollection: true) are accessed via member.collections:

PathDescription
member.collections.{slug}.countTotal number of records
member.collections.{slug}.latest.data.{field}Field value from the most recent record
member.collections.{slug}.latest.created_atTimestamp of the most recent record
member.collections.{slug}.latest.external_idExternal ID of the most recent record
member.collections.{slug}.recentList of the last 10 records (by creation time)

CEL expression examples:

member.collections.clinical_note.count > 0
member.collections.clinical_note.latest.data.note_type == "physician_note"
member.collections.clinical_note.recent.exists(r, r.data.note_type == "urgent")

Template examples:

Latest note: {{member.collections.clinical_note.latest.data.content}}
Total notes: {{member.collections.clinical_note.count}}

{% for note in member.collections.clinical_note.recent %}
- {{note.data.note_type}}: {{note.data.content}} ({{note.created_at}})
{% endfor %}

Best Practices

Data Type Design

  1. Use meaningful slugs: Slugs should be lowercase, hyphen-separated identifiers that reflect the data (e.g., patient, support-ticket, product-listing)
  2. Index strategically: Index fields you'll frequently search/filter by, but not everything (indexing has a performance cost)
  3. Mark external IDs: Always use external ID fields for data you sync from external systems
  4. Admin-only sensitive fields: Mark fields with sensitive info (internal notes, salary) as admin-only
  5. Plan collections carefully: Use isCollection: true only for data where members should have multiple records

Working with Permissions

  1. Principle of least privilege: Give members only the permissions they need
  2. Data type-level access: Use role permissions to control which data types members can access
  3. Record-level enforcement: Members automatically only see their own records unless they're managers
  4. Test access: Verify a member can only access what they should

External IDs

  1. Always provide external IDs when syncing data: Makes reconciliation and updates easier
  2. Use your system's IDs: Make external IDs match your source system (EMR ID, CRM ID, etc.)
  3. Handle collisions: If an external ID already exists, update the existing record rather than creating a duplicate
  4. Index external ID fields: Mark the field as shouldIndex: true for fast lookups

Example: Patient Management System

# 1. Create Patient data type
curl -X POST https://api.gravityrail.com/api/v2/w/{workspace_uuid}/data-types \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Patient",
"slug": "patient",
"description": "Patient records",
"isCollection": false,
"fields": [
{
"name": "EMR ID",
"slug": "emr_id",
"fieldType": "text",
"required": true,
"shouldIndex": true,
"isExternalId": true
},
{
"name": "First Name",
"slug": "first_name",
"fieldType": "text",
"required": true,
"shouldIndex": true
},
{
"name": "Last Name",
"slug": "last_name",
"fieldType": "text",
"required": true,
"shouldIndex": true
},
{
"name": "Date of Birth",
"slug": "date_of_birth",
"fieldType": "date",
"required": false,
"shouldIndex": false
},
{
"name": "Contact Phone",
"slug": "contact_phone",
"fieldType": "phone",
"required": false,
"shouldIndex": true
},
{
"name": "Medical History",
"slug": "medical_history",
"fieldType": "textarea",
"required": false,
"isAdminOnly": false
},
{
"name": "Internal Notes",
"slug": "internal_notes",
"fieldType": "textarea",
"required": false,
"isAdminOnly": true
}
]
}'

# 2. Create Clinical Notes data type (collection)
curl -X POST https://api.gravityrail.com/api/v2/w/{workspace_uuid}/data-types \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Clinical Note",
"slug": "clinical_note",
"description": "Clinical notes for patient encounters",
"isCollection": true,
"fields": [
{
"name": "Date",
"slug": "date",
"fieldType": "datetime",
"required": true,
"shouldIndex": true
},
{
"name": "Note Type",
"slug": "note_type",
"fieldType": "select",
"required": true,
"shouldIndex": true
},
{
"name": "Content",
"slug": "content",
"fieldType": "textarea",
"required": true,
"shouldIndex": false
}
]
}'

# 3. Create a role for clinical staff
curl -X POST https://api.gravityrail.com/api/v2/w/{workspace_uuid}/member-roles \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Clinical Staff",
"permissions": [
"data:read:patient",
"data:write:patient",
"data:read:clinical_note",
"data:write:clinical_note"
]
}'

# 4. Create a patient member + patient record
curl -X POST https://api.gravityrail.com/api/v2/w/{workspace_uuid}/create-signup \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"member": {
"email": "alice@example.com",
"name": "Alice Patient",
"role": "Patient"
},
"data": {
"patient": {
"externalId": "EMR-789",
"values": {
"emr_id": "EMR-789",
"first_name": "Alice",
"last_name": "Patient",
"date_of_birth": "1990-01-15",
"contact_phone": "+1-555-123-4567"
}
}
}
}'

# 5. Add a clinical note
curl -X POST https://api.gravityrail.com/api/v2/w/{workspace_uuid}/data-types/789/records \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fieldValues": {
"date": "2024-01-20T14:30:00Z",
"note_type": "physician_note",
"content": "Patient reports improved symptoms..."
}
}'

API Reference

For complete API documentation, see API Reference.

Key endpoints:

  • GET /w/{workspace_uuid}/data-types - List data types
  • POST /w/{workspace_uuid}/data-types - Create data type
  • GET /w/{workspace_uuid}/data-types/{data_type_id}/records - Query records
  • POST /w/{workspace_uuid}/data-types/{data_type_id}/records - Create record
  • PUT /w/{workspace_uuid}/data-types/{data_type_id}/records/{record_id} - Update record
  • DELETE /w/{workspace_uuid}/data-types/{data_type_id}/records/{record_id} - Delete record
  • GET /w/{workspace_uuid}/files - List files
  • POST /w/{workspace_uuid}/files - Upload file
  • DELETE /w/{workspace_uuid}/files/{file_id} - Delete file