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. Iffalse, 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 Type | Description | Example |
|---|---|---|
text | Short text (strings) | "John Doe", "Support Agent" |
textarea | Long text (paragraphs) | Multi-line notes |
email | Email address | "alice@example.com" |
phone | Phone number | "+1-555-123-4567" |
number | Integer or decimal | 42, 3.14 |
date | Date (YYYY-MM-DD) | "2024-01-15" |
datetime | Date + time (ISO 8601) | "2024-01-15T10:30:00Z" |
checkbox | Boolean (true/false) | true, false |
select | Single selection from predefined options | "active", "inactive" |
multiselect | Multiple selections | ["red", "blue", "green"] |
link | URL | "https://example.com" |
json | Arbitrary 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:
- Regular Members: Can view and edit only their own records
- Workspace Managers: Can view and edit any record
- 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
- Navigate to your workspace's "Data" section
- Click "Create Data Type"
- Enter name, slug, and description
- Add fields (choose field type, set required/indexed)
- 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:
| Path | Description |
|---|---|
member.collections.{slug}.count | Total number of records |
member.collections.{slug}.latest.data.{field} | Field value from the most recent record |
member.collections.{slug}.latest.created_at | Timestamp of the most recent record |
member.collections.{slug}.latest.external_id | External ID of the most recent record |
member.collections.{slug}.recent | List 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
- Use meaningful slugs: Slugs should be lowercase, hyphen-separated identifiers that reflect the data (e.g.,
patient,support-ticket,product-listing) - Index strategically: Index fields you'll frequently search/filter by, but not everything (indexing has a performance cost)
- Mark external IDs: Always use external ID fields for data you sync from external systems
- Admin-only sensitive fields: Mark fields with sensitive info (internal notes, salary) as admin-only
- Plan collections carefully: Use
isCollection: trueonly for data where members should have multiple records
Working with Permissions
- Principle of least privilege: Give members only the permissions they need
- Data type-level access: Use role permissions to control which data types members can access
- Record-level enforcement: Members automatically only see their own records unless they're managers
- Test access: Verify a member can only access what they should
External IDs
- Always provide external IDs when syncing data: Makes reconciliation and updates easier
- Use your system's IDs: Make external IDs match your source system (EMR ID, CRM ID, etc.)
- Handle collisions: If an external ID already exists, update the existing record rather than creating a duplicate
- Index external ID fields: Mark the field as
shouldIndex: truefor 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 typesPOST /w/{workspace_uuid}/data-types- Create data typeGET /w/{workspace_uuid}/data-types/{data_type_id}/records- Query recordsPOST /w/{workspace_uuid}/data-types/{data_type_id}/records- Create recordPUT /w/{workspace_uuid}/data-types/{data_type_id}/records/{record_id}- Update recordDELETE /w/{workspace_uuid}/data-types/{data_type_id}/records/{record_id}- Delete recordGET /w/{workspace_uuid}/files- List filesPOST /w/{workspace_uuid}/files- Upload fileDELETE /w/{workspace_uuid}/files/{file_id}- Delete file