Integrations
Data Stores
Persist state between integration runs with managed data stores
Data Stores
Data Stores are managed tables for persisting state between runs. They support typed columns and are accessed from step code via ctx.stores.
Column Types
| Type | Description |
|---|---|
TEXT | String value |
NUMBER | Numeric value (returned as string — convert with parseInt() or parseFloat()) |
BOOLEAN | Boolean value |
DATE | ISO 8601 date/datetime string |
JSON | Arbitrary JSON object |
Create a Store
curl -X POST https://app.headlessops.ai/api/stores \
-H "Authorization: Bearer iter_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "processed-invoices",
"description": "Tracks invoices already processed by the billing pipeline",
"columns": [
{ "name": "invoice_id", "type": "TEXT", "required": true },
{ "name": "processed_at", "type": "DATE", "required": false },
{ "name": "amount", "type": "NUMBER", "required": false }
]
}'Using Stores in Steps
Access stores via ctx.stores.STORE_NAME. Underscores in attribute names are automatically converted to hyphens for slug lookup:
// ctx.stores.processed_invoices → looks up slug "processed-invoices"
const store = ctx.stores.processed_invoices;Insert a Row
const row = await ctx.stores.processed_invoices.insert({
invoice_id: "inv_123",
processed_at: "2026-01-15T10:00:00Z",
amount: 4999,
});
ctx.log("inserted", { id: row.id });Find Records
// Find one — returns object or null
const record = await ctx.stores.processed_invoices.find_one({ invoice_id: "inv_123" });
if (record) {
return { status: "already_processed" };
}
// Find many — returns array
const results = await ctx.stores.processed_invoices.find({ status: "pending" });Update Records
const result = await ctx.stores.processed_invoices.update(
{ invoice_id: "inv_123" },
{ status: "sent" }
);
ctx.log("updated", { count: result.updated });Delete Records
await ctx.stores.processed_invoices.delete({ invoice_id: "inv_123" });Idempotency Pattern
Always check before inserting to make your automations safe to retry:
export async function run(input: Record<string, any>, ctx: StepContext) {
const orderId = input.order_id;
const existing = await ctx.stores.processed_orders.find_one({ order_id: orderId });
if (existing) {
ctx.log("already processed, skipping", { order_id: orderId });
return { status: "skipped" };
}
// ... do the work ...
await ctx.stores.processed_orders.insert({
order_id: orderId,
processed_at: "2026-01-15T10:00:00Z",
});
return { status: "success" };