What is snill.ai?
Custom business software from a conversation.
Snill lets you describe what your business needs in plain language — and generates a complete internal system in seconds. You get structured data, dashboards, forms, a REST API, and an AI assistant that can query and update your data.
It's built for founders, consultants, and operators who need custom software but don't want to build it from scratch or duct-tape together a stack of SaaS tools.
What's included
Every Snill app comes with the following out of the box — no add-ons, no integrations to wire up:
- AI-generated apps — describe what your business does, get a complete system instantly.
- Relational data model — collections, indexed lookups, calculated fields, and server-side filtering.
- Dashboards & custom pages — KPIs, charts, tables, and configurable views.
- AI Data Assistant — ask questions in plain English, get real answers and reports.
- Multi-user with roles — invite your team, control access per collection and per field.
- REST API for every app — with an auto-generated OpenAPI 3.0 spec, no extra setup.
- Outbound webhooks & triggers — HMAC-signed events to integrate with anything.
- Activity log & version history — full audit trail with one-click schema rollback.
- Import & export — CSV and JSON, for migration and backups.
- Themes — change the look and feel with a single setting.
How it works
- Describe your business — tell the AI what you need. "I run a consulting firm and need to track clients, projects, time entries, and invoices."
- Review the result — Snill generates a complete data model with collections, fields, relationships, dashboards, and navigation.
- Start using it — your app is live immediately. Add records, view dashboards, invite team members.
- Refine as you go — ask the AI to make changes ("add a status field to projects") or use the visual editor to tweak fields, layouts, and settings.
The AI and the visual editor produce the same output. You can switch between them freely — edits from either are fully compatible.
Creating your first app
From idea to working app in under a minute.
When you sign in to snill.ai, you'll see a text field where you describe what you need. Be as specific or as vague as you like — the AI handles the details.
What to write
Describe your business domain and what you want to track. Good prompts include the types of things you manage and how they relate to each other:
- "I run a property management company. I need to track properties, tenants, leases, and maintenance requests."
- "We're a recruitment agency. Track candidates, job openings, interviews, and placements."
- "Personal CRM for my consulting business — clients, projects, invoices, and time tracking."
What you get
The AI generates a complete app with:
- Collections — one for each type of thing you mentioned (e.g. properties, tenants)
- Fields — appropriate fields for each collection with the right types and formats
- Relationships — connections between collections (e.g. a tenant belongs to a property)
- A dashboard — summary stats and charts based on your data
- Navigation — a sidebar with links to each collection and the dashboard
Everything is editable. You can rename fields, add new collections, change the dashboard — either through the AI chat or the visual Appmodel Editor.
Collections
The building blocks of your app — each one holds a type of data.
A collection is like a database table or a spreadsheet tab. It holds records of the same type — for example, a "Customers" collection holds customer records, and an "Invoices" collection holds invoices.
Each collection has:
- A list view — a table showing all records with searchable, sortable columns
- A detail view — a form for viewing and editing a single record
- Fields — the properties each record has (name, email, amount, date, etc.)
You control which fields appear as columns in the list view, which fields are searchable, and how records are sorted by default. Each collection also has a singular label (e.g. "Customer" for the "Customers" collection) used in buttons and form titles like "Add Customer".
Collection names are used in URLs and the API, so the AI picks short, descriptive names like customers, invoices, or time_entries.
Fields
Define what each record contains — from text and numbers to dates and files.
Every collection has a set of fields that define the shape of its records. Each field has a type, a display label, and optional formatting.
Field types
| Type | Description | Example |
|---|---|---|
| Text | Short or long text, email, URL | Name, description, website |
| Number | Decimal or whole number, with optional currency or percent format | Price, quantity, tax rate |
| Boolean | True/false checkbox | Active, paid, approved |
| Date / Time | Date, date-time, or time-only (HH:mm) | Due date, created at, shift start |
| Dropdown | Pick from a fixed set of options | Status: draft, active, archived |
| Image / File | Upload an image or document | Photo, contract PDF |
| Lookup | A reference to a record in another collection | Customer on an invoice |
Special field behaviors
- Default values — fields can pre-fill with today's date, the current user, or a fixed value when creating a new record
- Unique — enforce that no two records have the same value (e.g. invoice numbers)
- Auto-generated — automatically create values like sequential IDs (
INV-001,INV-002) or unique identifiers - Hidden — keep a field in the data but hide it from forms (useful for system fields)
- Required — mark fields that must be filled in before saving
- Write-once — lock a field after the record is first saved. Useful for lookup fields like "Project" or "Customer" that shouldn't change after creation.
Relationships
Connect your collections so data flows between them naturally.
Most business data is connected — invoices belong to customers, tasks belong to projects, employees work in departments. Snill supports several relationship patterns:
Single lookup
The most common type. A field in one collection points to a record in another. For example, an invoice has a customer lookup field that links to the Customers collection. In the form, this renders as a searchable dropdown.
Multi-reference
When a record relates to multiple records in another collection. For example, a project might have several team members, each linking to the Employees collection.
Related collections
The reverse view — when viewing a customer, you can see all their invoices listed below the form. This is configured on the parent collection and shows child records that reference it. You can control which fields display, how they're sorted, and whether users can create new child records directly.
Self-referencing (tree view)
A collection can reference itself to create hierarchies. For example, a "Categories" collection where each category has a parent field pointing to another category. Snill renders this as an expandable tree instead of a flat list.
Linked fields
When you create a lookup, you can also pull in field values from the referenced record. For example, when selecting a product on an order line, automatically copy the product's price into a unit price field. This can either copy once (at creation) or stay in sync when the source changes.
Calculated fields
Automatically compute values from other fields using formulas.
A calculated field updates its value whenever the fields it depends on change. You define a formula, and Snill handles the rest.
Simple formulas
Reference other fields in the same record with arithmetic:
quantity * unitPrice
subtotal * $vatRate
The $ prefix references global app variables — constants like tax rates or hourly rates that you define once and use across formulas.
Aggregations
Calculated fields can also summarize data from related collections. If you have an Invoice with many Line Items, you can calculate totals on the invoice:
| Function | What it does | Example |
|---|---|---|
SUM | Total of all values | SUM(lineItems.amount) |
AVG | Average | AVG(reviews.rating) |
COUNT | Number of records | COUNT(tasks) |
MIN | Smallest value | MIN(bids.amount) |
MAX | Largest value | MAX(offers.price) |
Aggregations update automatically when child records are created, edited, or deleted.
You can combine aggregations with arithmetic. For example, compute a grand total: SUM(lineItems.amount) * $vatRate
Templates
Build display strings from record values using simple interpolation.
A template field assembles text from other fields automatically. Use the $(fieldName) syntax to insert values:
Dear $(contactName), your order $(orderNumber) is ready.
This is useful for creating readable display names, reference strings, or formatted summaries. Templates support dot notation for lookup fields — for example, $(customer.name) pulls the name from a linked customer record.
You can also reference app variables with $variableName, so a template like $(name) — $companyName would include your company name.
Form rules
Make forms dynamic — show, hide, or change fields based on conditions.
Form rules let you control field behavior based on the values of other fields. Each rule has a condition and one or more actions:
- Show / Hide — make fields visible or hidden based on a condition (e.g. show "overtime reason" only when hours exceed 8)
- Required — make a field mandatory only when certain conditions are met
- Read-only — lock fields from editing (e.g. make all fields read-only when status is "approved")
- Set value — automatically fill in a field value when a condition is true (e.g. set
approved dateto today when status changes to "approved")
Rules evaluate instantly as the user types or selects values — no save required. Each rule can also have an else block for the opposite case.
Rules are evaluated in order. If multiple rules affect the same field, later rules take precedence.
Form layouts
Organize fields into sections and columns for a clean editing experience.
By default, fields stack in a single column. With form layouts, you can:
- Group fields into sections — each section gets a heading label and can contain any number of fields
- Set column counts — arrange fields in a 1 to 6 column grid, per section or for the whole form
- Span columns — individual fields can span multiple columns (e.g. a "Notes" textarea spanning the full width)
For example, a contact form might have a "Contact Info" section with name, email, and phone in 3 columns, and an "Address" section below with street, city, zip, and country in 2 columns.
Fields not assigned to any group render at the bottom in the default layout.
Component groups
Form groups can also contain page-style components (charts, stats, tables) alongside regular fields. This lets you embed live data visualizations directly within a record's form — for example, showing a chart of related invoices right next to the customer's contact details.
Pages
Build custom views with filters, charts, tables, and text — beyond the standard collection views.
Pages are custom views that combine multiple components into a single screen. While collections give you list and detail views, pages let you create dashboards, reports, and overviews that pull data from multiple collections.
Components
A page is a grid of components. Each component has a type and a width (1-4 columns on the grid):
- Stats — single-number KPIs (record counts, sums, averages)
- Charts — bar, line, area, pie, donut, radar, and more
- Tables — filtered data tables from any collection
- Text and headings — static content for context and labels
- Links and buttons — navigation to collections, external URLs, or create-record actions
- HTML templates — custom Handlebars templates that render with collection data, displayed in a sandboxed iframe with Tailwind CSS support. Useful for printable reports, custom cards, or branded layouts.
Parameters
Pages can have interactive filters at the top. When users change a filter, all components on the page update to reflect the selection. Parameter types include:
- Text input, dropdowns (single or multi-select), date pickers, date ranges, number ranges, checkboxes, and collection lookups
Parameters can have smart defaults like "today's date" or "start of this month" — so the page shows relevant data immediately.
Date filters
Components can use dynamic date values in their queries — like "today", "start of this month", or "7 days ago". These update automatically, so a page showing "this month's sales" always shows the current month.
Pages appear in the sidebar navigation just like collections. You can even set a page as the app's start page.
Dashboards
A summary view of your most important data — stats, charts, and tables at a glance.
Every app has a dashboard that provides an overview of your data. It's built from widgets arranged on a 4-column grid:
Widget types
| Widget | What it shows |
|---|---|
| Count | Total number of records in a collection |
| Stat | An aggregate value — sum, average, min, or max of a field |
| Chart | Data grouped and visualized — supports bar, line, area, pie, donut, radar, and more |
| Table | A mini table showing recent or filtered records |
| Activity | A feed of recent changes across the app |
Charts can group data by any field — including dates with configurable granularity (day, week, month, year). Each widget can span 1 to 4 columns for flexible layouts.
The AI generates a sensible dashboard automatically, but you can customize it through the visual editor or by asking the AI to change it.
Access control
Control who can see, edit, and delete data — at the collection, record, and field level.
Collection-level access
Each collection can restrict access by role. There are three modes:
- Everyone — all users have full access (the default)
- Custom roles — specify which roles can read, edit, and delete. For example, only "admin" and "manager" can delete records, while "viewer" can only read.
- Admin only — only admins can access the collection. It won't appear in the sidebar for other users.
Record ownership
For collections where users should only see or edit their own records, you can set an ownership mode:
| Mode | See | Edit / Delete |
|---|---|---|
| All | All records | All records |
| Own-modify | All records | Only their own |
| Own | Only their own | Only their own |
Ownership is tracked automatically — the system records who created each record. Admins always have full access regardless of these settings.
Field-level access
Individual fields can be restricted too. For example, a "salary" field might be visible only to the "admin" and "hr" roles, and editable only by "admin".
Members & Billing
Snill is structured around organizations — your team's container for projects, members, and billing.
An organization is the top-level workspace. It contains one or more projects (each project is one app) and a list of members. Members have roles at the organization level:
- Admin — manages billing, invites and removes members, and can delete the organization.
- Member — uses projects and their data, but cannot manage the organization itself.
Inside each project you can also define your own custom roles (e.g. "manager", "viewer", "sales") used by access control rules — see Access control for collection-level and field-level permissions.
Billing & usage
Billing is at the organization level. Free plans include a small number of projects and a generous usage cap to try things out; paid plans unlock unlimited projects, higher record limits, and higher AI quotas, billed per active member. Manage subscription, payment method, and invoices under Settings → Billing (admins only).
Usage is tracked across the whole organization — record count, storage, and AI requests. All members can review usage under Settings → Usage.
Activity Log
A complete audit trail for everything that happens in your app.
The activity log records every data mutation (create, update, delete) along with custom events fired by triggers and action buttons. Each entry captures the user, action, collection, affected record, a short summary, and a timestamp.
Open it from the Activity Log item in the left sidebar. The view is searchable and paginated, with filters by action type and collection.
The activity log is also part of the public REST API. Your app's OpenAPI spec includes a GET /v1/system/activitylog endpoint — useful for exporting audit history or building integrations that watch for specific events. See the API & Integrations section.
Data Assistant
An AI chat that can query your data, generate reports, and modify your app.
The Data Assistant is a built-in AI that understands your app's data model. You can ask it questions in plain language:
- "Show me all invoices from this month that are unpaid"
- "What's the total revenue by customer for Q1?"
- "Create a chart of monthly sales trends"
Beyond querying, the assistant can also modify your app's structure — adding fields, creating new collections, adjusting layouts, and updating dashboard widgets. It's the same AI that built your app initially, so it understands the full context.
Structural changes (adding fields, modifying the data model) are only available to admin users. Non-admin users can query and explore data but cannot change the app's configuration.
Appmodel Editor
A visual and code-level editor for your app's entire configuration.
The Appmodel Editor gives admins full control over the app's data model — the JSON configuration that defines every collection, field, relationship, layout, and dashboard.
Visual editor
The visual editor lets you configure everything without touching JSON:
- Collection cards — view and edit each collection's fields, settings, and relationships
- Field editor — click any field to change its type, format, validations, lookup config, and access control
- Options editor — configure list fields, search fields, sort order, form layout, related collections, and more
- Sidebar, Dashboard, and Variables tabs — manage navigation, widgets, and global constants
JSON schema editor
For advanced users, the JSON tab provides a full code editor with:
- Schema validation — real-time error highlighting with detailed tooltips as you type
- Autocomplete — suggestions for field types, formats, and DSL extensions
- Version history — browse and restore any previous version of the data model, with details on what changed and whether the change was made by AI or manually
- Copy and download — export the full JSON for backup or reference
The Appmodel Editor is only available to admin users. Changes are validated before saving and create a new version — you can always roll back.
Import / Export
Bring your data in from CSV or JSON, and export it whenever you need.
Importing data
You can import records into any collection from CSV or JSON files. The import wizard guides you through three steps:
- Upload — select your CSV or JSON file
- Map fields — match columns from your file to fields in the collection. The wizard handles type coercion (e.g. converting text to numbers or dates) and can extend dropdown options if your data contains new values.
- Review and import — preview the data before importing. Large imports are processed in the background.
After importing, newly added records are highlighted in the list view so you can verify the results.
Exporting data
Export records from any collection as CSV or JSON. The export includes the currently filtered/searched data, so you can narrow down what you export.
Themes
Change the look and feel of your app with a single setting.
Snill comes with several built-in visual themes that change the colors and styling of your app. You can select a theme in the app settings or ask the AI to apply one.
The default theme works well for most use cases. Other options include a clean neutral grayscale theme and several color themes. Themes let you match your app to your brand or personal preference.
API & Integrations
Every Snill app comes with a complete REST API — auto-generated from your data model, with scoped API keys and OpenAPI/Swagger documentation built in.
Base URL and live spec
The API is served from a single host, regardless of which app it serves — the API key tells Snill which app to operate on. Each collection in your data model becomes a CRUD endpoint:
https://api.snill.ai/v1/data/{collection}
An interactive Swagger UI — with all your collections, fields, and types — is available at https://api.snill.ai/v1/docs/ui. The raw OpenAPI 3.0 spec is at https://api.snill.ai/v1/docs and is auto-generated from your data model. The spec is scoped to the API key you use, so a key with limited scopes only sees the collections and operations it can call.
An excerpt from a typical app's spec looks like this:
{
"openapi": "3.0.0",
"info": { "title": "Customers app", "version": "1.0.0" },
"servers": [{ "url": "/v1" }],
"paths": {
"/data/customers": {
"get": {
"summary": "List customers",
"parameters": [
{ "name": "q", "in": "query", "schema": { "type": "string" } },
{ "name": "sort", "in": "query", "schema": { "type": "string" } },
{ "name": "limit", "in": "query", "schema": { "type": "integer" } },
{ "name": "offset", "in": "query", "schema": { "type": "integer" } }
],
"responses": { "200": { "description": "OK" } }
},
"post": { "summary": "Create a customer", ... }
},
"/data/customers/{id}": {
"get": { "summary": "Get a customer" },
"patch": { "summary": "Update a customer" },
"delete": { "summary": "Delete a customer" }
}
}
}
Note that paths are relative to servers[0].url — so the full URL for the list endpoint above is https://api.snill.ai/v1/data/customers. Most OpenAPI tools (and AI assistants) handle the join automatically.
Using the spec with AI assistants
Because the OpenAPI spec is auto-generated and complete, you can hand it directly to an AI coding assistant — Claude, Codex, Cursor, ChatGPT, and others — and have it write integrations or query your data without reading the API surface yourself.
Fetch your spec once with your API key:
curl -H "X-API-Key: snill_01ARZ3NDEKTSV4RRFFQ69G5FAV_..." https://api.snill.ai/v1/docs > snill-spec.json
Then paste the spec into a prompt and describe what you want. For example:
- "Here's my Snill API spec [paste]. Write a Python script that fetches all overdue invoices and posts a Slack summary every Monday."
- "Here's my Snill API spec [paste]. Import these customer rows from this CSV [paste], deduping on email."
- "Here's my Snill API spec [paste]. Build me a one-off report of revenue by service line for last quarter."
The assistant uses the spec to pick the right endpoints, headers, and query syntax — no manual API reading required.
Authentication
External callers authenticate with an API key. You manage keys under Manage → API Keys in your app. Pass the key in either header on every request:
X-API-Key: snill_01ARZ3NDEKTSV4RRFFQ69G5FAV_a1b2c3d4e5f6789012345678901234ab
Authorization: Bearer snill_01ARZ3NDEKTSV4RRFFQ69G5FAV_a1b2c3d4e5f6789012345678901234ab
Keys can be granted one of two scope shapes:
- Full — read, create, update, and delete on every collection.
- Per-collection — an explicit list of allowed operations per collection. For example, a key scoped to
{ "customers": ["read"], "invoices": ["read", "create"] }can list and fetch customers, list/fetch/create invoices, and do nothing else.
Keys can be deactivated at any time or set to expire on a specific date. All requests are rate-limited to 60 requests per minute per key (current usage is returned in the X-RateLimit-Limit and X-RateLimit-Remaining response headers).
CRUD endpoints
The examples below use a customers collection. Substitute your own collection name in the URL.
List records
GET /v1/data/customers
?q={"status":"active"} # filter (JSON, URL-encoded)
&sort=-createdAt # sort (- prefix means descending)
&limit=20&offset=0 # pagination (default 20, max 1000)
Returns a JSON array of records. The response also includes X-Total-Count and X-Has-More headers so you can paginate without re-counting.
Get one record
GET /v1/data/customers/{id}
Create a record
POST /v1/data/customers
Content-Type: application/json
{ "name": "Acme", "email": "contact@acme.com" }
Returns 201 Created with the full record — including auto-generated fields, calculated fields, and resolved lookups.
Update a record
PATCH /v1/data/customers/{id}
Content-Type: application/json
{ "name": "Acme Inc" }
Partial — only fields in the body are updated. Returns the merged record. (Snill does not support PUT; PATCH is the only update verb.)
Delete a record
DELETE /v1/data/customers/{id}
Returns 204 No Content.
Querying and filtering
Pass filter conditions as a JSON object in the q parameter (URL-encoded). Snill supports operators familiar from MongoDB:
$gt,$gte,$lt,$lte— numeric and date comparisons$ne— not equal$in,$nin— match against a list$exists— field is present or not$regex— regular expression match
Sort with ?sort=name (ascending) or ?sort=-createdAt (descending). Combine multiple fields with commas: ?sort=status,-createdAt.
Paginate with ?limit=20&offset=40. The maximum limit is 1000; the default is 20.
Pagination is mandatory — there's no "fetch all" endpoint. For large collections, iterate with offset to walk through results in batches. The X-Total-Count header on every response tells you when you're done.
Errors
Errors return a JSON body with a human-readable message and a stable code:
{
"error": "API key does not have read access to this collection",
"code": "SCOPE_DENIED"
}
Common codes you'll see:
| Code | Status | Meaning |
|---|---|---|
MISSING_API_KEY | 401 | No X-API-Key or Authorization header on the request. |
INVALID_API_KEY | 401 | The key isn't recognized. |
KEY_INACTIVE | 403 | The key has been deactivated. |
KEY_EXPIRED | 403 | The key's expiresAt date has passed. |
SCOPE_DENIED | 403 | The key doesn't have the required operation on this collection. |
RATE_LIMIT_EXCEEDED | 429 | You've exceeded 60 requests per minute on this key. |
Snill can also push notifications outward — POSTing record changes to URLs you configure, with HMAC-signed payloads. See Webhooks for details.
Webhooks
Notify external systems when data in your app changes.
Outbound webhooks let you POST a payload to any HTTPS URL whenever a record is created, updated, or deleted — and when custom events fire from triggers or action buttons. You configure them under Manage → Webhooks: pick the collection, the events to listen for, and the URL to call. Up to 10 webhooks per project.
Each delivery is a JSON POST containing the event name, collection name, the current record (and the previous record for updates), and a project identifier. The request is signed with an X-Snill-Signature: sha256=... header — an HMAC-SHA256 of the raw request body using the webhook's secret. Verify the signature on your end before trusting the payload.
Deliveries are queued and retried on non-2xx responses. Delivery history (status, response code, latency) is visible in the Webhooks tab, where you can also send test events while developing your integration.
Verifying signatures
Every delivery includes an X-Snill-Signature header. Verify it by computing HMAC-SHA256 of the raw request body using your webhook's secret, and comparing it to the header value:
// Node.js
const crypto = require('crypto');
function verifyWebhook(rawBody, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
# Python
import hmac, hashlib
def verify_webhook(raw_body, signature, secret):
expected = 'sha256=' + hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(signature, expected)
Always verify against the raw request body, not a re-serialized object — even a whitespace difference will change the hash. Use constant-time comparison (timingSafeEqual / compare_digest) to prevent timing attacks.
Triggers
Fire custom events on data changes — declaratively, no code required.
Triggers are rules attached to a collection that emit named events when conditions are met. Two kinds:
- Record triggers — fire on create, update, or delete with an optional condition. For example: "Fire
order-approvedonly whenstatuschanges toapproved." - Aggregate triggers — fire when a count, sum, average, min, or max across a collection crosses a threshold. For example: "Fire
low-stock-alertwhen the count of products withstock < 10exceeds 5."
You define triggers visually in the Appmodel Editor when editing a collection — no code needed. The events they emit are picked up by webhooks (so external systems can react) and recorded in the activity log (for debugging and audit).