Asana
Emulated Asana REST API (v1.0) for local development and testing.
A stateful, in-memory Asana REST API (v1.0) emulator.
Included now:
- Users, workspaces, teams, projects, sections, tasks, tags, stories, webhooks
- Subtasks, task dependencies/dependents, followers, project/section membership
- Bearer-token auth and offset-based pagination (
limit1–100,offset)
Follow-up work will add OAuth 2.0 and an inspector UI.
# From this repo (after `bun install && bun run build`)bun packages/emulate/dist/index.js --service asana
# Or from the published packagenpx @pleaseai/emulate --service asanaA single service starts on the base port (default 4000). Use -p <port> to
change it. When started alongside other services, ports are assigned
sequentially from the base port (asana’s slot is 4005).
Default URL (asana alone):
http://localhost:4000All /api/1.0/* routes require a bearer token. Any token works — an unmatched
token resolves to the first seeded user, so me keywords work out of the box.
curl http://localhost:4000/api/1.0/users/me \ -H "Authorization: Bearer test-token"Map specific tokens to users via the top-level tokens key in the config:
tokens: test-token: login: dev@example.com # matches a seeded user's email, gid, or name scopes: []Conventions
Section titled “Conventions”- Write bodies are wrapped in
data:{"data": {"name": "My Task"}}. - Successful responses are wrapped in
data; lists add anext_pagecursor. - Errors return
{"errors": [{"message": "..."}]}.
Example
Section titled “Example”# Create a task (assigned to the authenticated user)curl -X POST http://localhost:4000/api/1.0/tasks \ -H "Authorization: Bearer test-token" \ -H "Content-Type: application/json" \ -d '{"data":{"name":"Write the README","projects":["<project_gid>"],"assignee":"me"}}'
# List tasks in a projectcurl "http://localhost:4000/api/1.0/tasks?project=<project_gid>&limit=50" \ -H "Authorization: Bearer test-token"Seed Config
Section titled “Seed Config”Add an asana: section to emulate.config.yaml (or pass --seed <file>).
Inter-entity references are resolved by name; the workspace defaults to the
first one when omitted.
asana: workspaces: - name: My Workspace is_organization: true users: - name: Developer email: dev@example.com teams: - name: Engineering workspace: My Workspace projects: - name: My Project workspace: My Workspace team: Engineering owner: Developer sections: - name: To Do project: My Project tags: - name: urgent workspace: My Workspace color: red tasks: - name: Example Task project: My Project section: To Do assignee: Developer due_on: 2026-01-31