---
title: "Tidy for developers: API, MCP, workflows and virtual employees"
description: "Tidy's platform, shown working: 2,000+ REST routes, 200+ MCP tools, rules, workflows in Firecracker micro-VMs, and employees on your own machines."
url: https://www.tidy.engineering/dev
last_updated: 2026-10-13T13:57:12.000Z
---

- S Syncs
- D Data sets
- A Automations
- W Workflows
- E Employees
- G Your agents
- X Surfaces

Tidy for developers Some assembly encouraged

# Build your own kind of tidy.

Tidy already holds your email, calendar, files and tasks, and knows how they connect. Now your code, your agents and your virtual employees can build on it.

` claude mcp add --transport http tidy $TIDY/api/v1/mcp`

[Request an invite](https://www.tidy.engineering/#waitlist)

Connect Claude Code, or any MCP client, to your Tidy. `$TIDY` is your Tidy address. Tidy is in private beta. [See plans](https://www.tidy.engineering/pricing)

*A Tidy workspace showing the All Leads table of a CRM. Three kinds of background work are connected to it: a travel-time recipe adds a Leave by event to the calendar, an employee named Penny works a reconcile-invoices order on her own machine, and a workflow in a micro-VM fills in a lead's company.*

One workspace, and three kinds of background work changing it: a recipe adds a calendar event, an employee picks up a work order, a workflow fills in a lead.

- 2,000+REST routes in one OpenAPI 3.1 spec
- 200+MCP tools one catalog, two transports
- 28 rule actions for when this, then that
- 13 view types for any object
- 7 agent harnesses for your employees

S Syncs

## Your data is already flowing in.

Email, calendar and contacts stay in sync on their own, and your archives come in through importers for Evernote, Notion, MBOX, ICS and VCF. Your own systems write into the same contacts and generic records through one idempotent door. Subscribe to the changes you need.

Arrives on its own example activity

- Email 42 new · 9 held by the screener 2m
- Calendar 6 changed · 1 conflict found 5m
- Contacts 3 updated 11m
- Drive 12 files imported 1h
- Evernote 2,118 notes imported 1h

one graph: the same rows the assistant, your rules and your employees read

What your code writes POST /api/v1/capture

crm_export.sh bash

```
# crm_export.sh: write a CRM export into Tidy's Lead facet.
# Same email, same row. Same idempotency key, nothing written twice.
curl -X POST "$TIDY/api/v1/capture" \
  -H "Authorization: Bearer $TIDY_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "facet_slug": "lead",
    "target_table": "contacts",
    "match_field": "email",
    "values": {
      "email": "tom@harbordental.example",
      "name": "Tom Okafor",
      "company": "Harbor Dental",
      "lifecycle_stage": "Qualified"
    },
    "source": "crm-export",
    "idempotency_key": "crm-export-2026-10-12-0042"
  }'
```

$ sh crm_export.sh

{"target_id":"8f3c…","outcome":"created",…}

$ sh crm_export.sh # again

{"target_id":"8f3c…","outcome":"replay",…}

Out the other side

- `Tidy-Signature` signed webhooks, per event type
- `GET /api/v1/events/stream` a live event stream
- MCP for the agents you connect

Sync runs on every plan; history, note imports and storage grow with it. API access, personal access tokens and event subscriptions come with Max. [Compare plans](https://www.tidy.engineering/pricing)

D Data sets

## A facet, plus every way to see it.

A facet is a schema over one of Tidy's shared building blocks: a contact, a task, a note, an event, a file. Your custom objects live in the same tables as your email and calendar, so the assistant can fill them, search them and act on them. Then you look at them through whichever of Tidy's 13 view types their fields can fill.

1. **1** **Capture** Anything you jot down, dictate or forward.
2. **2** **Extract** Tidy fills the Lead's fields from what you wrote.
3. **3** **Lenses** One record, in every view its facet supports.

*A dictated note about Rosa Lindqvist of Juniper Physio becomes a Lead: name, company, email, stage Engaged, source note. The same record then shows in the Leads Pipeline kanban under Engaged, as a row in the All Leads table, and as a new bar in the Leads by Source chart.*

capture record MCP

```
// Tidy reads the Lead facet's own field instructions,
// creates the record when it is confident, and links
// it into the note it came from. Use your note's id.
{ "tool": "facet_create_from_content",
  "arguments": {
    "parent_id": "N-6c1ed470-9200-417c-a68b-7f403cd91927",
    "facet_kind": "lead",
    "content": "Met Rosa Lindqvist at the trade show. She runs Juniper Physio, two clinics, and wants a quote for online booking by Friday. rosa@juniperphysio.example"
  } }
```

record lens MCP

```
// The same records through another lens: a kanban,
// grouped by the facet's stage.
{ "tool": "database_view_create",
  "arguments": {
    "container": { "type": "workspace" },
    "title": "Leads Pipeline",
    "facet": "lead",
    "layout": "kanban"
  } }
```

### A workstation is one business process, set up.

It is a switcher entry with its own navigation, views and create menu. The **Tidy for CRM** template installs one, along with the Lead facet, its forms, twelve saved views and three rules you switch on when you are ready. You see everything it will create before anything is written.

[See the CRM template](https://www.tidy.engineering/templates/crm)

Facets and views are on every plan.

A Automations

## When this happens, do the next thing.

A rule is a trigger, a few conditions and an ordered list of actions, checked against your live schema when you save it. Human-written rules run as their creator. An employee's rule runs as its human owner and waits for that owner's approval unless the employee has permission to switch rules on. A rule's writes never set off another rule unless you say so. Your agent can write one in a single call.

- created
- property_changed
- deleted
- button
- scheduled
- link_added
- system_event
- email
- plugin_event

rule

trigger

conditions

actions, in order

runs as its human actor
re-firing off by default

- create_linked
- mutate_property
- notify
- send_sms
- send_email_draft
- ask_assistant
- ai_transform
- run_workflow
- wake_project_ve
- call_endpoint

9 trigger kinds 10 of 28 actions

one call writes the rule MCP · automation_upsert

```
// Use your Harbor Dental project's UUID below.
// Bob must be an unambiguous member name in your workspace.
{ "tool": "automation_upsert",
  "arguments": {
    "slug": "pipeline-handoff-harbor-dental",
    "name": "Pipeline handoff",
    "facet_slug": "task",
    "enabled": true,
    "trigger": {
      "kind": "property_changed", "field": "status",
      "scope": "to", "value": "done" },
    "conditions": { "op": "and", "clauses": [
      { "field": "project", "operator": "equals",
        "value": "2d531ad4-f40e-4d8e-91ad-ec57f2931688" } ] },
    "actions": [
      { "verb": "create_linked", "params": {
          "facet": "task", "link_kind": "depends_on",
          "title": "Review: {{trigger.item.title}}",
          "owner": "Bob" } },
      { "verb": "notify", "params": {
          "member": "Bob",
          "message": "{{trigger.item.title}} is done." } }
    ] } }
```

pipeline-handoff-harbor-dental *active*

**When** a task's status changes to done

**If** its project is Harbor Dental

**Then** create a linked task "Review: …" for Bob

notify Bob that it is done

checked against the task facet's schema

automation_runs

pipeline-handoff-harbor-dental

succeeded · Send two-chair quote done

create_linked "Review: Send two-…"

notify Bob

A saved rule is not a proven rule. `automation_runs` shows each firing with what every action resolved to.

### Tidy's own recipes run on the same events.

Travel time watches your calendar. With a home address, a meeting location and a route, it adds or updates a “Leave for …” event when the meeting moves: the drive, plus ten minutes. Recipes that message you keep to a daily attention budget. A recipe's SMS is limited to 450 characters. Critical recipes bypass the attention budget.

*A calendar afternoon: the Harbor Dental meeting moves from 4:00 to 4:45 PM, and travel time adds Leave for Harbor Dental from 4:05 to 4:45.*

Automations are on Plus. [Automation in the app](https://www.tidy.engineering/automation)

W Workflows

## Isolated code, durable retries.

A workflow is a Python script with a trigger: a change to your data, a schedule, a button or a rule. Each run boots its own Firecracker micro-VM under Temporal. If the machine under it dies, Temporal notices the silence and runs it again in a fresh one.

run log

run wf-4821 · enrich_leads.py · trigger: manual

attempt 1 of 3

▸ micro-vm restored from snapshot ok

▸ egress: tidy api, api.enrich.example:443

▸ python enrich_leads.py

enriched 14 of 31 leads

✕ worker lost: no heartbeat for 30s

attempt 2 of 3 · retry in 1s

▸ micro-vm restored from snapshot ok

▸ python enrich_leads.py

enriched 31 of 31 leads

✓ completed · exit 0 · 3.8s

How a run survives a lost machine. The script and its output are an example.

- **A micro-VM per run** Each run gets its own Firecracker micro-VM, restored from a snapshot and torn down after.
- **Egress bounded per run** Its own network namespace. The Tidy API gateway, DNS, and allowed external hosts resolved to IPs on TCP 443. An unresolved host is skipped, never widened.
- **Credentials by dispatch** Your workflow gets a token for you that expires in five minutes, plus any plugin credentials you attach. Generated-artifact code runs on a separate path with no credentials.
- **A hard budget** At most three hundred seconds of script time per attempt, then it is stopped.
- **Durable retries** The worker sends a heartbeat every five seconds. Thirty seconds of silence triggers a retry, up to three attempts. A failed script still reports its failure.
- **Every run on the record** Status, exit code, output and duration, through the API.

the runs, over REST bash

```
# Run a workflow now, then read its runs.
curl -X POST "$TIDY/api/v1/workflows/$WORKFLOW_ID/execute" \
  -H "Authorization: Bearer $TIDY_PAT"
# 202  {"execution_id": "…", "status": "queued"}

curl "$TIDY/api/v1/workflows/$WORKFLOW_ID/executions" \
  -H "Authorization: Bearer $TIDY_PAT"
# [{"status": "completed", "exit_code": 0, "duration_ms": 3812,
#   "stdout": "enriched 31 of 31 leads\n", …}]
```

Inside the micro-VM

**API_BASE**

the Tidy API, through this run's gateway

**AUTH_TOKEN**

delegated to you, gone in five minutes

**INPUTS_JSON**

inputs, when supplied by the caller

A retry starts your script from the top, so write it to be safe to run twice. The capture door in Syncs is idempotent for exactly this reason.

Workflows are on Plus. [Automation in the app](https://www.tidy.engineering/automation)

E Employees

## Employees that work on your machines.

A virtual employee is a member of your workspace with its own identity. It runs as a service on a computer you choose, which Tidy calls a workshop, in the harness you pick. You hand it work with a mention or a typed work order, you watch what it costs, and you set how far it may go. Move it to another workshop and its identity and work stay in Tidy.

**workshop** studio-nas · linux heartbeat 12s ago

**employee**

penny · Claude Code

**status**

Working

**task**

reconcile Q3 invoices 3 of 5

# harbor-dental

Priya@penny can you reconcile the Q3 invoices before Friday?

penny On it. Five invoices; I'll post each one here as I finish.

Autonomy board tokens and cost, last 24 hours

employee state tokens cost

penny *Working* 12.4k$0.31

ravi *Backing off* 3.1k$0.08

sam *Offline*––

A dash means the machine has not reported in full. Tidy never shows an unknown cost as $0.00.

You set the ceiling. Where two dials disagree, the more restrictive one wins. An example setting:

Runtime mode

1. Supervised
2. Auto-edits
3. Full access

Execution posture

1. Fully gated
2. Per-tool-call
3. Auto-allow with judgment
4. Full permission

Sandbox level

1. Open
2. Guarded
3. Workspace
4. Strict

typed work, over REST bash

```
# Hand Penny typed work, and say what you expect back.
curl -X POST "$TIDY/api/v1/ve/work-orders" \
  -H "Authorization: Bearer $TIDY_PAT" \
  -H "Content-Type: application/json" \
  -d '{
    "agent_member_slug": "penny",
    "action": "Reconcile the Q3 invoices against the bank export",
    "inputs": { "folder": "Finance/Q3" },
    "expected_output_schema": {
      "type": "object", "required": ["matched", "flagged"],
      "properties": { "matched": { "type": "integer" },
                      "flagged": { "type": "array" } } },
    "timeout_s": 14400
  }'
# 201  {"work_order_id": "…", "status": "waiting", "timeout_at": "…"}
```

Harnesses

- Pi
- Claude Code
- Codex
- Hermes
- opencode
- openclaw
- Antigravity

Hermes, openclaw and Antigravity run on Linux and macOS workshops.

[Set up an employee](https://www.tidy.engineering/virtual-employees)

Virtual employees are on Plus.

G Your agents

## Connect your own agent. Your scopes set the boundary.

Tidy's MCP server is the front door for Claude Code, Codex or anything that speaks MCP: one catalog of 200+ tools, served remotely and over stdio. Your token's scopes decide what your agent can see. Anything past them goes back to you for consent.

terminal

$ claude mcp add --transport http tidy $TIDY/api/v1/mcp

tools/list

search memory:search everything, one query

calendar_propose_time calendar:write answer an invite

automation_upsert primitives:write write a rule

memory_search memory:search recall what it learned

email_send email:send send, you pick who to

… and the rest of the catalog your scopes allow

two transports, one catalog bash

```
# Remote: Streamable HTTP with OAuth. Clients discover the rest from
#   $TIDY/.well-known/oauth-protected-resource/api/v1/mcp
claude mcp add --transport http tidy $TIDY/api/v1/mcp

# Local: the same catalog over stdio, from the Tidy CLI (private beta).
tidy auth login
tidy mcp stdio
```

The moment it needs you

**agent** calls `calendar_propose_time`

**tidy** 403 · needs `calendar:write`

**client** restarts OAuth for the extra scope

**you** approve it on Tidy's consent screen

**agent** calls `email_send` with no recipient

**tidy** Who should receive this? *asked of you, not the agent*

**you** priya@acme.example

**audit** email_send · to `blake2b:4be1…09c2`

Sensitive elicited answers are hashed before they reach the audit log. Already-authorized calls do not need fresh consent; asking for a missing recipient requires a client that supports elicitation.

MCP is on every plan. The tools an agent can use are the ones its token's scopes allow, and the catalog is not the whole API: some routes have no tool yet.

X Surfaces

## Four ways in, one contract.

One committed OpenAPI 3.1 spec describes the whole API, and the SDKs are generated from it. Each way in says what it costs and how finished it is.

REST API Max

2,000+ routes in one OpenAPI 3.1 spec. Personal access tokens from Settings, Security, Tokens.

POST /api/v1/capture

POST /api/v1/records

POST /api/v1/ve/work-orders

GET /api/v1/events/stream SSE

[Max plan](https://www.tidy.engineering/pricing)

MCP Every plan

200+ tools over a remote endpoint with OAuth, or locally over stdio. Scopes decide what an agent sees.

POST $TIDY/api/v1/mcp OAuth

tidy mcp stdio local

How consent works

Webhooks In: Plus · Out: Max

In: a plugin's Events door takes a delivery signed, with a bearer token or by URL secret, and sets off your rules. Out: the event types you subscribe to, signed.

in POST /api/v1/plugin-events/{plugin_id}

out Tidy-Signature: t=1760280000,v1=5f3c…

HMAC-SHA256 · 300s window · 7-day rotation

**Verify a delivery in Python**

verify_tidy_webhook.py python

```
import hashlib, hmac, time

def verify(header: str, raw_body: bytes, secret: str) -> bool:
    """Check a Tidy-Signature header against the exact bytes received."""
    try:
        parts = dict(p.split("=", 1) for p in header.split(","))
        t, v1 = int(parts["t"]), parts["v1"]
        mac = hmac.new(secret.encode(), f"{t}.".encode() + raw_body,
                       hashlib.sha256).hexdigest()
        return abs(int(time.time()) - t) <= 300 and hmac.compare_digest(mac, v1)
    except (ValueError, KeyError, TypeError):
        return False
```

Tidy signs `"{t}." + raw_body` with your subscription's secret. Check the exact bytes you received, before any JSON parsing. During a seven-day rotation, try both secrets.

CLI and SDKs Private beta

A signed CLI for sign-in, the local MCP server and audit checks. Python and TypeScript SDKs, generated from the spec: typed errors, retries with backoff.

$ tidy audit verify --export=audit.jsonl

✓ Verified 1204 entries; chain intact.

Not on a public package registry yet.

Own your AI

## Your data, your call.

Tidy is local-first: synced workspace data lives on your device, so you can read and edit it offline. Inference and other server calls still need a connection. You are the customer, not the product. We don't sell your data and we don't train on it.

**reads**

from your device, offline too

**memory**

every recall runs for exactly one person

**access**

employees read for you only through a grant, and every use is logged

**training**

none

your context

**Tidy inference** Tidy supplies the models. No keys to manage.

**Your provider sign-in** Employees can use a provider account you already have.

**Your own hardware** The same work on a GPU you own. *roadmap*

Running Tidy's models on your own hardware through a desktop gateway is the direction we are building toward, not something that ships today. [Planning hardware with us](https://www.tidy.engineering/consulting) is available now.

## Any data. Any automation. Any employee. Yours.

Tidy is in private beta. Request an invite to see whether Tidy is right for you.

[Request an invite](https://www.tidy.engineering/#waitlist) [Set up an employee](https://www.tidy.engineering/virtual-employees)

S Syncs D Data sets A Automations W Workflows E Employees G Your agents X Surfaces
