emulate
v0.11.2
Published
Local drop-in replacement services for CI and no-network sandboxes
Maintainers
Readme
emulate
Local drop-in replacement services for CI and no-network sandboxes. Fully stateful, production-fidelity API emulation. Not mocks.
Quick Start
npx emulateAll services start with sensible defaults. No config file needed:
- Vercel on
http://localhost:4000 - GitHub on
http://localhost:4001 - Google on
http://localhost:4002 - Slack on
http://localhost:4003 - Apple on
http://localhost:4004 - Microsoft on
http://localhost:4005 - Okta on
http://localhost:4006 - AWS on
http://localhost:4007 - Resend on
http://localhost:4008 - Stripe on
http://localhost:4009 - MongoDB Atlas on
http://localhost:4010 - Clerk on
http://localhost:4011 - Linear on
http://localhost:4012 - Twilio on
http://localhost:4013
Stripe webhooks configured with a secret include a Stripe-Signature header signed over the timestamp and raw request body.
Resend POST /emails and POST /emails/batch support 24-hour Idempotency-Key replay, returning the original email IDs without duplicate emails or webhooks.
CLI
# Start all services (zero-config)
npx emulate
# Start specific services
npx emulate --service vercel,github
# Custom port
npx emulate --port 3000
# Use a seed config file
npx emulate --seed config.yaml
# Generate omitted service secrets into a private file
npx emulate start --seed config.yaml --generated-secrets-file .emulate-secrets.json
# Generate a starter config
npx emulate init
# Generate config for a specific service
npx emulate init --service vercel
# List available services
npx emulate listOptions
| Flag | Default | Description |
|------|---------|-------------|
| -p, --port | 4000 | Base port (auto-increments per service) |
| -s, --service | all | Comma-separated services to enable |
| --seed | auto-detect | Path to seed config (YAML or JSON) |
| --base-url | none | Override advertised base URL (supports {service} template) |
| --portless | off | Serve over HTTPS via portless (auto-registers aliases) |
| --generated-secrets-file | none | Generate omitted service secrets and write them to a new owner-only JSON file |
The port can also be set via EMULATE_PORT or PORT environment variables.
HTTPS with portless
portless gives emulators trusted HTTPS URLs with auto-generated certs and no browser warnings.
# Start the portless proxy (first time only)
portless proxy start
# Start emulate with portless integration
npx emulate start --portlessEach service registers as a portless alias and gets a named HTTPS URL:
github https://github.emulate.localhost
google https://google.emulate.localhost
slack https://slack.emulate.localhostIf portless is not installed, emulate will prompt to install it (npm i -g portless).
The --portless flag overwrites any existing portless aliases matching *.emulate. Aliases are removed automatically when emulate shuts down.
For a custom base URL without portless (any reverse proxy), use --base-url or the EMULATE_BASE_URL env var:
npx emulate start --base-url "https://{service}.myproxy.test"The PORTLESS_URL env var is automatically set by the portless CLI wrapper when running a command through it (e.g. portless github.emulate emulate start), typically to a value like https://{service}.emulate.localhost. It supports {service} interpolation, just like --base-url and EMULATE_BASE_URL. When no explicit baseUrl is provided, it is used as a fallback.
Per-service overrides are also supported in the seed config (these take highest priority over all other base URL sources):
github:
baseUrl: https://github.emulate.localhostProgrammatic API
npm install emulateEach call to createEmulator starts a single service:
import { createEmulator } from 'emulate'
const github = await createEmulator({ service: 'github', port: 4001 })
const vercel = await createEmulator({ service: 'vercel', port: 4002 })
github.url // 'http://localhost:4001'
vercel.url // 'http://localhost:4002'
await github.close()
await vercel.close()When a GitHub App omits private_key, createEmulator generates an RSA-2048 PKCS#1 key for that emulator instance:
const github = await createEmulator({
service: 'github',
seed: {
github: {
users: [{ login: 'octocat' }],
apps: [{
app_id: 12345,
slug: 'my-github-app',
name: 'My GitHub App',
installations: [{ installation_id: 100, account: 'octocat' }],
}],
},
},
})
const privateKey = github.generatedSecrets.find(
secret => secret.kind === 'github.app_private_key' && secret.id === '12345',
)?.valueGenerated keys remain stable across reset() calls and appear only in generatedSecrets. Explicitly configured keys are never returned there. A new createEmulator call generates a new key.
The CLI can also generate omitted GitHub App keys when a delivery file is requested:
npx emulate start --service github --seed config.yaml \
--generated-secrets-file .emulate-secrets.jsonThe destination must not exist. emulate removes inherited ACLs, verifies effective owner-only access, and publishes complete JSON before opening listeners or configuring portless. Handled startup failures remove the invocation-owned artifact so the command can be retried immediately. A hard termination such as SIGKILL can leave a complete published artifact that must be removed manually after confirming no invocation is using it. Only generated secrets are included. Explicitly configured keys are never copied into the artifact. Linux requires setfacl and getfacl from the acl package. The flag fails closed when access controls cannot be verified and is not supported on Windows. Without --generated-secrets-file, CLI seed files keep requiring private_key.
Vitest / Jest setup
// vitest.setup.ts
import { createEmulator, type Emulator } from 'emulate'
let github: Emulator
let vercel: Emulator
beforeAll(async () => {
;[github, vercel] = await Promise.all([
createEmulator({ service: 'github', port: 4001 }),
createEmulator({ service: 'vercel', port: 4002 }),
])
process.env.GITHUB_EMULATOR_URL = github.url
process.env.VERCEL_EMULATOR_URL = vercel.url
})
afterEach(() => { github.reset(); vercel.reset() })
afterAll(() => Promise.all([github.close(), vercel.close()]))Options
| Option | Default | Description |
|--------|---------|-------------|
| service | (required) | Service name: 'vercel', 'github', 'google', 'slack', 'apple', 'microsoft', 'okta', 'aws', 'resend', 'stripe', 'mongoatlas', 'clerk', 'linear', or 'twilio' |
| port | 4000 | Port for the HTTP server |
| seed | none | Inline seed data (same shape as YAML config) |
| baseUrl | none | Override advertised base URL. Per-service baseUrl in seed config takes highest priority, then this option, then EMULATE_BASE_URL env var (supports {service}), then PORTLESS_URL (supports {service}, automatically set by the portless CLI wrapper), then http://localhost:<port>. |
Instance methods
| Method | Description |
|--------|-------------|
| url | Base URL of the running server |
| generatedSecrets | Readonly secrets generated while preparing seed data |
| reset() | Wipe the store and replay seed data |
| close() | Shut down the HTTP server, returns a Promise |
Configuration
Configuration is optional. The CLI auto-detects config files in this order: emulate.config.yaml / .yml, emulate.config.json, service-emulator.config.yaml / .yml, service-emulator.config.json. Or pass --seed <file> explicitly. Run npx emulate init to generate a starter file.
tokens:
my_token:
login: admin
scopes: [repo, user]
vercel:
users:
- username: developer
name: Developer
email: [email protected]
teams:
- slug: my-team
name: My Team
projects:
- name: my-app
team: my-team
framework: nextjs
github:
users:
- login: octocat
name: The Octocat
email: [email protected]
orgs:
- login: my-org
name: My Organization
members:
- login: octocat
role: admin
repos:
- owner: octocat
name: hello-world
language: JavaScript
auto_init: true
google:
users:
- email: [email protected]
name: Test User
- email: [email protected]
name: Admin
hd: acme.com
oauth_clients:
- client_id: my-client-id.apps.googleusercontent.com
client_secret: GOCSPX-secret
redirect_uris:
- http://localhost:3000/api/auth/callback/google
labels:
- id: Label_ops
user_email: [email protected]
name: Ops/Review
color_background: "#DDEEFF"
color_text: "#111111"
messages:
- id: msg_welcome
user_email: [email protected]
from: [email protected]
to: [email protected]
subject: Welcome to the Gmail emulator
body_text: You can now test Gmail, Calendar, and Drive flows locally.
label_ids: [INBOX, UNREAD, CATEGORY_UPDATES]
calendars:
- id: primary
user_email: [email protected]
summary: [email protected]
primary: true
selected: true
time_zone: UTC
calendar_events:
- id: evt_kickoff
user_email: [email protected]
calendar_id: primary
summary: Project Kickoff
start_date_time: 2025-01-10T09:00:00.000Z
end_date_time: 2025-01-10T09:30:00.000Z
drive_items:
- id: drv_docs
user_email: [email protected]
name: Docs
mime_type: application/vnd.google-apps.folder
parent_ids: [root]
slack:
team:
name: My Workspace
domain: my-workspace
users:
- name: developer
real_name: Developer
email: [email protected]
profile:
title: Local Developer
status_text: Testing locally
status_emoji: ":computer:"
presence: active
channels:
- name: general
topic: General discussion
- name: random
topic: Random stuff
bots:
- name: my-bot
oauth_apps:
- client_id: "12345.67890"
client_secret: example_client_secret
app_id: A000000001
name: My Slack App
redirect_uris:
- http://localhost:3000/api/auth/callback/slack
scopes:
- chat:write
- channels:read
- channels:history
- channels:join
- channels:manage
- channels:write
- groups:read
- groups:history
- groups:write
- im:read
- im:history
- im:write
- mpim:read
- mpim:history
- mpim:write
- users:read
- users:read.email
- users.profile:read
- users.profile:write
- users:write
- files:read
- files:write
- pins:read
- pins:write
- bookmarks:read
- bookmarks:write
- reactions:read
- reactions:write
- team:read
user_scopes: [users:read, users.profile:read]
bot_name: my-bot
tokens:
- token: xoxb-local-test
user: developer
scopes:
- chat:write
- channels:read
- channels:history
- channels:join
- channels:manage
- channels:write
- groups:read
- groups:history
- groups:write
- im:read
- im:history
- im:write
- mpim:read
- mpim:history
- mpim:write
- users:read
- users:read.email
- users.profile:read
- users.profile:write
- users:write
- files:read
- files:write
- pins:read
- pins:write
- bookmarks:read
- bookmarks:write
- reactions:read
- reactions:write
- team:read
strict_scopes: false
linear:
organization:
name: Acme
url_key: acme
users:
- email: [email protected]
name: Admin User
admin: true
- email: [email protected]
name: Developer
teams:
- key: ENG
name: Engineering
states:
- name: Backlog
type: backlog
- name: Todo
type: unstarted
- name: In Progress
type: started
- name: Done
type: completed
labels:
- name: Bug
color: "#d92d20"
team: ENG
- name: Feature
color: "#2563eb"
team: ENG
issues:
- team: ENG
title: Fix local checkout test
description: Reproduce and fix the checkout failure.
state: Todo
assignee: [email protected]
labels: [Bug]
oauth_apps:
- client_id: lin_example_client_id
client_secret: example_client_secret
name: My Linear App
redirect_uris:
- http://localhost:3000/api/auth/callback/linear
scopes: [read, write, issues:create, comments:create]
actor: user
tokens:
- token: lin_test_admin
user: [email protected]
scopes: [read, write, issues:create, comments:create, admin]
strict_scopes: false
apple:
users:
- email: [email protected]
name: Test User
oauth_clients:
- client_id: com.example.app
team_id: TEAM001
name: My Apple App
redirect_uris:
- http://localhost:3000/api/auth/callback/apple
microsoft:
users:
- email: [email protected]
name: Test User
oauth_clients:
- client_id: example-client-id
client_secret: example-client-secret
name: My Microsoft App
redirect_uris:
- http://localhost:3000/api/auth/callback/microsoft-entra-id
aws:
region: us-east-1
s3:
buckets:
- name: my-app-bucket
- name: my-app-uploads
sqs:
queues:
- name: my-app-events
- name: my-app-dlq
iam:
users:
- user_name: developer
create_access_key: true
roles:
- role_name: lambda-execution-role
description: Role for Lambda function execution
okta:
users:
- login: [email protected]
email: [email protected]
first_name: Test
last_name: User
groups:
- name: Everyone
description: All users
type: BUILT_IN
okta_id: 00g_everyone
authorization_servers:
- id: default
name: default
audiences: [api://default]
oauth_clients:
- client_id: okta-test-client
client_secret: okta-test-secret
name: Sample OIDC Client
redirect_uris:
- http://localhost:3000/callback
auth_server_id: default
resend:
domains:
- name: example.com
region: us-east-1
contacts:
- email: [email protected]
first_name: Test
last_name: User
stripe:
customers:
- email: [email protected]
name: Test Customer
products:
- name: Pro Plan
description: Monthly pro subscription
prices:
- product_name: Pro Plan
currency: usd
unit_amount: 2000
mongoatlas:
projects:
- name: Project0
clusters:
- name: Cluster0
project: Project0
database_users:
- username: admin
project: Project0
databases:
- cluster: Cluster0
name: test
collections: [items]
clerk:
users:
- first_name: Test
last_name: User
email_addresses: [[email protected]]
password: clerk_test_password
organizations:
- name: My Company
slug: my-company
members:
- email: [email protected]
role: admin
oauth_applications:
- client_id: clerk_emulate_client
client_secret: clerk_emulate_secret
name: Emulate App
redirect_uris:
- http://localhost:3000/api/auth/callback/clerk
twilio:
account:
sid: AC00000000000000000000000000000000
auth_token: twilio_test_auth_token
friendly_name: Local Twilio Account
api_keys:
- sid: SK00000000000000000000000000000000
secret: twilio_test_api_secret
friendly_name: Local API Key
phone_numbers:
- phone_number: "+15551234567"
friendly_name: Local SMS and Voice Number
sms_url: http://localhost:3000/api/twilio/sms
voice_url: http://localhost:3000/api/twilio/voice
messaging_services:
- friendly_name: Local Messaging Service
phone_numbers: ["+15551234567"]
verify_services:
- friendly_name: Local Verify Service
code: "123456"
default_channel: sms
conversations:
services:
- friendly_name: Local ConversationsGitHub organization members are optional. Each entry references a seeded user by login; role defaults to member, while admin creates an organization administrator. Unknown users are ignored. Seeded memberships use the synthetic members team and grant private organization repository access.
OAuth & Integrations
The emulator supports configurable OAuth apps and integrations with strict client validation.
Vercel Integrations
vercel:
integrations:
- client_id: "oac_abc123"
client_secret: "secret_abc123"
name: "My Vercel App"
redirect_uris:
- "http://localhost:3000/api/auth/callback/vercel"GitHub OAuth Apps
github:
oauth_apps:
- client_id: "Iv1.abc123"
client_secret: "secret_abc123"
name: "My Web App"
redirect_uris:
- "http://localhost:3000/api/auth/callback/github"If no oauth_apps are configured, the emulator accepts any client_id (backward-compatible). With apps configured, strict validation is enforced.
GitHub Apps
Full GitHub App support with JWT authentication and installation access tokens:
github:
apps:
- app_id: 12345
slug: "my-github-app"
name: "My GitHub App"
permissions:
contents: read
issues: write
events: [push, pull_request]
webhook_url: "http://localhost:3000/webhooks/github"
webhook_secret: "my-secret"
installations:
- installation_id: 100
account: my-org
repository_selection: allJWT authentication: sign a JWT with { iss: "<app_id>" } using the app's private key (RS256). The emulator verifies the signature and resolves the app. For programmatic createEmulator calls, omit private_key and read the generated RSA key from generatedSecrets. The CLI generates an omitted key only when --generated-secrets-file <path> is provided; otherwise it requires an explicit, valid private key. Do not replace the omitted field with a fake PEM placeholder.
Installation access tokens act as the configured GitHub App bot for repository writes. Repository ownership, selected repository access, and requested App permissions remain enforced. Pull request merges require contents: write on the base repository. Pull request branch updates require pull_requests: write on the pull request repository and contents: write on the head repository.
Inspect secret-free metadata for minted installation tokens at GET /_emulate/installation-tokens.
App webhook delivery: When events occur on repos where a GitHub App is installed, the emulator mirrors real GitHub behavior:
- All webhook payloads (including repo and org hooks) include an
installationfield with{ id, node_id }. - If the app has a
webhook_url, the emulator delivers the event there with theinstallationfield and (if configured) anX-Hub-Signature-256header signed withwebhook_secret.
Slack OAuth Apps
slack:
oauth_apps:
- client_id: "12345.67890"
client_secret: "example_client_secret"
name: "My Slack App"
redirect_uris:
- "http://localhost:3000/api/auth/callback/slack"Linear OAuth Apps
linear:
oauth_apps:
- client_id: "lin_example_client_id"
client_secret: "example_client_secret"
name: "My Linear App"
redirect_uris:
- "http://localhost:3000/api/auth/callback/linear"
scopes: [read, write, issues:create, comments:create]
actor: userApple OAuth Clients
apple:
oauth_clients:
- client_id: "com.example.app"
team_id: "TEAM001"
name: "My Apple App"
redirect_uris:
- "http://localhost:3000/api/auth/callback/apple"Microsoft OAuth Clients
microsoft:
oauth_clients:
- client_id: "example-client-id"
client_secret: "example-client-secret"
name: "My Microsoft App"
redirect_uris:
- "http://localhost:3000/api/auth/callback/microsoft-entra-id"Vercel API
Every endpoint below is fully stateful with Vercel-style JSON responses and cursor-based pagination.
User & Teams
GET /v2/user- authenticated userPATCH /v2/user- update userGET /v2/teams- list teams (cursor paginated)GET /v2/teams/:teamId- get team (by ID or slug)POST /v2/teams- create teamPATCH /v2/teams/:teamId- update teamGET /v2/teams/:teamId/members- list membersPOST /v2/teams/:teamId/members- add member
Projects
POST /v11/projects- create project (with optional env vars and git integration)GET /v10/projects- list projects (search, cursor pagination)GET /v9/projects/:idOrName- get project (includes env vars)PATCH /v9/projects/:idOrName- update projectDELETE /v9/projects/:idOrName- delete project (cascades)GET /v1/projects/:projectId/promote/aliases- promote aliases statusPATCH /v1/projects/:idOrName/protection-bypass- manage bypass secrets
Deployments
POST /v13/deployments- create deployment (auto-transitions to READY)GET /v13/deployments/:idOrUrl- get deployment (by ID or URL)GET /v6/deployments- list deployments (filter by project, target, state)GET /v7/deployments- list deployments (filter by project, target, state, commit SHA)DELETE /v13/deployments/:id- delete deployment (cascades)PATCH /v12/deployments/:id/cancel- cancel building deploymentGET /v2/deployments/:id/aliases- list deployment aliasesGET /v3/deployments/:idOrUrl/events- get build events/logsGET /v6/deployments/:id/files- list deployment filesPOST /v2/files- upload file (by SHA digest)
Domains
POST /v10/projects/:idOrName/domains- add domain (with verification challenge)GET /v9/projects/:idOrName/domains- list domainsGET /v9/projects/:idOrName/domains/:domain- get domainPATCH /v9/projects/:idOrName/domains/:domain- update domainDELETE /v9/projects/:idOrName/domains/:domain- remove domainPOST /v9/projects/:idOrName/domains/:domain/verify- verify domain
Environment Variables
GET /v10/projects/:idOrName/env- list env vars (with decrypt option)POST /v10/projects/:idOrName/env- create env vars (single, batch, upsert)GET /v10/projects/:idOrName/env/:id- get env varPATCH /v9/projects/:idOrName/env/:id- update env varDELETE /v9/projects/:idOrName/env/:id- delete env var
Blob
Implements the Vercel Blob API used by the @vercel/blob SDK (put, head, list, del).
PUT /api/blob?pathname=<path>- upload a blob (honorsx-add-random-suffix,x-allow-overwrite,x-content-type,x-cache-control-max-age,x-if-matchheaders)GET /api/blob?url=<urlOrPathname>- blob metadata (head())GET /api/blob?prefix=&limit=&cursor=&mode=- list blobs (list(), including folded mode)POST /api/blob/delete- delete blobs (del())GET /blob/:storeId/<pathname>- serve blob content (public, no auth;?download=1adds an attachment disposition)
Point the SDK at the emulator with two environment variables:
VERCEL_BLOB_API_URL=http://localhost:4000/api/blob
BLOB_READ_WRITE_TOKEN=vercel_blob_rw_mystore_secretAny token of the form vercel_blob_rw_<storeId>_<secret> is accepted; the store id is parsed from the token. Multipart uploads and client (browser) uploads are not supported yet.
GitHub API
Every endpoint below is fully stateful. Creates, updates, and deletes persist in memory and affect related entities.
Users
GET /user- authenticated userPATCH /user- update profileGET /users/:username- get userGET /users- list usersGET /users/:username/repos- list user reposGET /users/:username/orgs- list user orgsGET /users/:username/followers- list followersGET /users/:username/following- list following
Repositories
GET /repos/:owner/:repo- get repoGET /repositories/:id- get repo by numeric IDPOST /user/repos- create user repoPOST /orgs/:org/repos- create org repoPATCH /repos/:owner/:repo- update repoDELETE /repos/:owner/:repo- delete repo (cascades)GET/PUT /repos/:owner/:repo/topics- get/replace topicsGET /repos/:owner/:repo/languages- languagesGET /repos/:owner/:repo/contributors- contributorsGET /repos/:owner/:repo/forks- list forksPOST /repos/:owner/:repo/forks- create forkGET/PUT/DELETE /repos/:owner/:repo/collaborators/:username- collaboratorsGET /repos/:owner/:repo/collaborators/:username/permissionPOST /repos/:owner/:repo/transfer- transfer repoGET /repos/:owner/:repo/tags- list tags
Contents & Commit History
GET /repos/:owner/:repo/readme- get the repository READMEGET /repos/:owner/:repo/contents/:path- get a file or list a directory at a ref- Send
Accept: application/vnd.github.raworapplication/vnd.github.raw+jsonto file Contents and README requests to receive raw bytes; directory and submodule responses remain JSON GET /:owner/:repo/raw/:ref/:path- download file content from advertised raw URLs; this is separate from Accept negotiationPUT/DELETE /repos/:owner/:repo/contents/:path- create, update, or delete a file and commit the changeGET /repos/:owner/:repo/commits- list commits with ref, path, author, and date filtersGET /repos/:owner/:repo/commits/:ref- get a commit with file diffs and statsGET /repos/:owner/:repo/compare/:base...:head- compare two refs
Issues
GET /repos/:owner/:repo/issues- list (filter by state, labels, assignee, milestone, creator, since)POST /repos/:owner/:repo/issues- createGET /repos/:owner/:repo/issues/:number- getPATCH /repos/:owner/:repo/issues/:number- update (state transitions, events)PUT/DELETE /repos/:owner/:repo/issues/:number/lock- lock/unlockGET /repos/:owner/:repo/issues/:number/timeline- timeline eventsGET /repos/:owner/:repo/issues/:number/events- eventsPOST/DELETE /repos/:owner/:repo/issues/:number/assignees- manage assignees
Pull Requests
GET /repos/:owner/:repo/pulls- list (filter by state, head, base)POST /repos/:owner/:repo/pulls- createGET /repos/:owner/:repo/pulls/:number- getPATCH /repos/:owner/:repo/pulls/:number- updatePUT /repos/:owner/:repo/pulls/:number/merge- merge (with branch protection enforcement)GET /repos/:owner/:repo/pulls/:number/commits- list commitsGET /repos/:owner/:repo/pulls/:number/files- list filesPOST/DELETE /repos/:owner/:repo/pulls/:number/requested_reviewers- manage reviewersPUT /repos/:owner/:repo/pulls/:number/update-branch- update branch
Comments
- Issue comments: full CRUD on
/repos/:owner/:repo/issues/:number/comments - Review comments: full CRUD on
/repos/:owner/:repo/pulls/:number/comments - Commit comments: full CRUD on
/repos/:owner/:repo/commits/:sha/comments - Repo-wide listings for each type
Reviews
GET /repos/:owner/:repo/pulls/:number/reviews- listPOST /repos/:owner/:repo/pulls/:number/reviews- create (with inline comments)GET/PUT /repos/:owner/:repo/pulls/:number/reviews/:id- get/updatePOST /repos/:owner/:repo/pulls/:number/reviews/:id/events- submitPUT /repos/:owner/:repo/pulls/:number/reviews/:id/dismissals- dismiss
Labels & Milestones
- Labels: full CRUD, add/remove from issues, replace all
- Milestones: full CRUD, state transitions, issue counts
Branches & Git Data
- Branches: list, get, protection CRUD (status checks, PR reviews, enforce admins)
- Refs: get, match, create, update, delete
- Commits: get, create
- Trees: get (with recursive), create (with inline content)
- Blobs: get, create
- Tags: get, create
Organizations & Teams
- Orgs: get, update, list
- Org members: list, check, remove, get/set membership
- Teams: full CRUD, members, repos
Releases
- Releases: full CRUD, latest, by tag
- Release assets: full CRUD, upload
- Generate release notes
Webhooks
- Repo webhooks: full CRUD, ping, test, deliveries
- Org webhooks: full CRUD, ping
- Real HTTP delivery to registered URLs on all state changes
Search
GET /search/repositories- full query syntax (user, org, language, topic, stars, forks, etc.)GET /search/issues- issues + PRs (repo, is, author, label, milestone, state, etc.)GET /search/users- users + orgsGET /search/code- blob content searchGET /search/commits- commit message searchGET /search/topics- topic searchGET /search/labels- label search
Actions
- Workflows: list, get, enable/disable, dispatch
- Workflow runs: list, get, cancel, rerun, delete, logs
- Jobs: list, get, logs
- Artifacts: list, get, delete
- Secrets: repo + org CRUD
Checks
- Check runs: create, update, get, annotations, rerequest, list by ref/suite. Ref based lookups accept branch and tag refs containing slashes.
- Check suites: create, get, preferences, rerequest, list by ref. Ref based lookups accept branch and tag refs containing slashes.
- Automatic suite status rollup from check run results
Misc
GET /rate_limit- rate limit statusGET /meta- server metadataGET /octocat- ASCII artGET /emojis- emoji URLsGET /zen- random zen phraseGET /versions- API versions
Google OAuth + Gmail, Calendar, and Drive APIs
OAuth 2.0, OpenID Connect, and mutable Google Workspace-style surfaces for local inbox, calendar, and drive flows.
Google ID tokens are RS256-signed JWTs. The discovery document advertises RS256, and /oauth2/v3/certs returns the matching RSA public key used to verify issued tokens.
GET /o/oauth2/v2/auth- authorization endpointPOST /oauth2/token- token exchangeGET /oauth2/v2/userinfo- get user infoGET /.well-known/openid-configuration- OIDC discovery documentGET /oauth2/v3/certs- JSON Web Key Set (JWKS) with the RSA public key for ID token verificationGET /gmail/v1/users/:userId/messages- list messages withq,labelIds,maxResults, andpageTokenGET /gmail/v1/users/:userId/messages/:id- fetch a Gmail-style message payload infull,metadata,minimal, orrawformatsGET /gmail/v1/users/:userId/messages/:messageId/attachments/:id- fetch attachment bodiesPOST /gmail/v1/users/:userId/messages/send- create sent mail fromrawMIME or structured fieldsPOST /gmail/v1/users/:userId/messages/import- import inbox mailPOST /gmail/v1/users/:userId/messages- insert a message directlyPOST /gmail/v1/users/:userId/messages/:id/modify- add/remove labels on one messagePOST /gmail/v1/users/:userId/messages/batchModify- add/remove labels across many messagesPOST /gmail/v1/users/:userId/messages/:id/trashandPOST /gmail/v1/users/:userId/messages/:id/untrashGET /gmail/v1/users/:userId/drafts,POST /gmail/v1/users/:userId/drafts,GET /gmail/v1/users/:userId/drafts/:id,PUT /gmail/v1/users/:userId/drafts/:id,POST /gmail/v1/users/:userId/drafts/:id/send,DELETE /gmail/v1/users/:userId/drafts/:idPOST /gmail/v1/users/:userId/threads/:id/modify- add/remove labels across a threadGET /gmail/v1/users/:userId/threadsandGET /gmail/v1/users/:userId/threads/:idGET /gmail/v1/users/:userId/labels,POST /gmail/v1/users/:userId/labels,PATCH /gmail/v1/users/:userId/labels/:id,DELETE /gmail/v1/users/:userId/labels/:idGET /gmail/v1/users/:userId/history,POST /gmail/v1/users/:userId/watch,POST /gmail/v1/users/:userId/stopGET /gmail/v1/users/:userId/settings/filters,POST /gmail/v1/users/:userId/settings/filters,DELETE /gmail/v1/users/:userId/settings/filters/:idGET /gmail/v1/users/:userId/settings/forwardingAddresses,GET /gmail/v1/users/:userId/settings/sendAsGET /discovery/v1/apis/calendar/v3/rest— public Calendar v3 REST discovery documentGET /calendar/v3/users/:userId/calendarList,GET /calendar/v3/calendars/:calendarId/events,POST /calendar/v3/calendars/:calendarId/events,DELETE /calendar/v3/calendars/:calendarId/events/:eventId,POST /calendar/v3/freeBusyGET /drive/v3/files,GET /drive/v3/files/:fileId,POST /drive/v3/files,PATCH /drive/v3/files/:fileId,PUT /drive/v3/files/:fileId,POST /upload/drive/v3/files
Slack API
Fully stateful Slack Web API emulation with channels, messages, threads, reactions, user profiles, presence, modern file uploads, pins, bookmarks, views, OAuth v2, and incoming webhooks. Chat writes preserve common rich message fields such as blocks, attachments, metadata, formatting flags, unfurl flags, and client message ids. Conversation writes update archive state, names, topics, purposes, membership, DMs, MPIMs, and read cursors. User writes update profile fields, status, custom fields, and deterministic active or away presence. File writes support the current external upload flow with local upload URLs, file share messages, reads, lists, downloads, and deletes. Pin and bookmark writes support channel message pins and link bookmarks. View writes support App Home publishing and modal stacks. Seeded OAuth apps and OAuth installs create bot users and installation records. OAuth exchanges and explicit token seeds create scoped token records. Supported write state changes dispatch Slack event_callback payloads to configured webhook URLs.
Slack message text is limited to 40,000 Unicode characters across chat writes, incoming webhooks, and file upload initial comments. Longer text is truncated at a Unicode code point boundary before it is stored or dispatched. Successful Web API responses include warning: "message_truncated" and response_metadata with the matching warning and explanatory message. Rich fields such as blocks and attachments are preserved unchanged.
Auth & Chat
POST /api/auth.test- test authenticationPOST /api/chat.postMessage- post message with text or rich payload fields (supports threads viathread_tsand DM user IDs)POST /api/chat.postEphemeral- post ephemeral message outside channel historyPOST /api/chat.update- update message text and rich payload fieldsPOST /api/chat.delete- delete messageGET /api/chat.getPermalink/POST /api/chat.getPermalink- get message permalinkPOST /api/chat.scheduleMessage- schedule pending messagePOST /api/chat.deleteScheduledMessage- delete pending scheduled messagePOST /api/chat.scheduledMessages.list- list pending scheduled messagesPOST /api/chat.meMessage- /me message
Conversations
POST /api/conversations.list- list conversations (cursor pagination,types,exclude_archived)POST /api/conversations.info- get channel infoPOST /api/conversations.create- create channelPOST /api/conversations.archive/conversations.unarchive- archive/restore channelPOST /api/conversations.rename- rename channelPOST /api/conversations.setTopic/conversations.setPurpose- update topic/purposePOST /api/conversations.history- channel history with rich message fieldsPOST /api/conversations.replies- thread replies with rich message fieldsPOST /api/conversations.join/conversations.leave- join/leavePOST /api/conversations.invite/conversations.kick- manage membershipPOST /api/conversations.open/conversations.close- open/close DMs and MPIMsPOST /api/conversations.mark- mark read cursorPOST /api/conversations.members- list members
Users & Reactions
POST /api/users.list- list users (cursor pagination)POST /api/users.info- get user infoPOST /api/users.lookupByEmail- lookup by emailGET /api/users.profile.get/POST /api/users.profile.get- get user profile fieldsPOST /api/users.profile.set- update profile fields, status, and custom fieldsGET /api/users.getPresence/POST /api/users.getPresence- get active or away presencePOST /api/users.setPresence- set the authed user to away or automatic presencePOST /api/reactions.add/reactions.remove/reactions.get- manage reactions
Files
POST /api/files.getUploadURLExternal- create a local external upload sessionPOST /upload/v1/:fileId- receive raw uploaded file bytesPOST /api/files.completeUploadExternal- complete uploads and optionally share file messagesGET /api/files.info/POST /api/files.info- get file metadataGET /api/files.list/POST /api/files.list- list completed filesGET /files-pri/:fileId/:filename- download file bytes with a bearer token that can access the filePOST /api/files.delete- delete a completed file
Pins & Bookmarks
POST /api/pins.add- pin a message to a channelGET /api/pins.list/POST /api/pins.list- list pinned message items for a channelPOST /api/pins.remove- remove a message pin from a channelPOST /api/bookmarks.add- add a link bookmark to a channelPOST /api/bookmarks.edit- update a link bookmarkPOST /api/bookmarks.list- list channel bookmarksPOST /api/bookmarks.remove- remove a bookmark from a channel
Views
POST /api/views.publish- publish or update an App Home view for a userPOST /api/views.open- open a modal viewPOST /api/views.update- update a view byview_idorexternal_idPOST /api/views.push- push a modal view onto the current modal stackPOST /api/views.generateTriggerId- local helper for tests that need a modal trigger id
Modal opens and pushes require values from /api/views.generateTriggerId. Pass the returned value as trigger_id or interactivity_pointer; generate push values with an existing view_id and use them within 3 seconds.
Team, Bots & Webhooks
POST /api/team.info- workspace infoPOST /api/bots.info- bot infoPOST /services/:teamId/:botId/:webhookId- incoming webhook with text or rich payload fields
OAuth
GET /oauth/v2/authorize- authorization (shows user picker)POST /oauth/v2/authorize/callback- local user picker callback that creates the auth codePOST /api/oauth.v2.access- token exchange
Inspector
GET /- tabbed local inspector for conversations, messages, files, views, auth records, incoming webhooks, event subscriptions, and event deliveries
Slack scope checks are relaxed by default so local tests can use simple bearer tokens. Set slack.strict_scopes: true in seed config to make supported Web API methods return Slack-style missing_scope errors with needed and provided fields. Strict mode checks chat:write, channels:read, channels:history, channels:join, channels:manage, channels:write, groups:read, groups:history, groups:write, im:read, im:history, im:write, mpim:read, mpim:history, mpim:write, users:read, users:read.email, users.profile:read, users.profile:write, users:write, files:read, files:write, pins:read, pins:write, bookmarks:read, bookmarks:write, reactions:read, reactions:write, and team:read. Slack lists no method-specific scopes for views.publish, views.open, views.update, or views.push, so the emulator requires auth but does not add strict-scope checks for those methods.
Current Slack limits: Slack Connect, Enterprise Grid admin APIs, Audit Logs API, SCIM, Legal Holds, Socket Mode, slash command and interaction simulation, user groups, reminders, stars, calls, canvases, lists, functions, workflows, chat streaming, legacy files.upload, exact rate limiting, and paid-plan behavior are not implemented.
Linear API
Stateful Linear GraphQL API emulation with seeded organizations, users, teams, workflow states, issues, comments, labels, projects, cycles, OAuth apps, tokens, webhooks, and basic agent sessions. GraphQL reads and writes mutate in-memory state and use Relay-style connections with opaque cursors. OAuth supports authorization code, PKCE, refresh token, revoke, client credentials, and actor=app tokens for local app-actor tests. Supported writes dispatch Linear-shaped webhook payloads with Linear-Delivery, Linear-Event, and Linear-Signature headers when webhooks are configured.
GraphQL
POST /graphql- GraphQL endpoint for queries and mutationsGET /graphql- query-string GraphQL endpoint for tooling- Queries:
viewer,organization,users,user,teams,team,workflowStates,workflowState,issues,issue,comments,comment,issueLabels,issueLabel,projects,project,cycles,cycle,webhooks,webhook,agentSessions,agentSession - Mutations:
issueCreate,issueUpdate,issueDelete,issueArchive,issueUnarchive,commentCreate,commentUpdate,commentDelete,issueLabelCreate,issueLabelUpdate,issueLabelDelete,issueAddLabel,issueRemoveLabel,webhookCreate,webhookDelete,agentSessionCreateOnIssue,agentSessionCreateOnComment,agentSessionUpdate,agentActivityCreate
Issue selections expose both numeric priority and Linear's derived priorityLabel values: No priority, Urgent, High, Medium, and Low.
OAuth
GET /oauth/authorize- authorization endpoint with local user pickerPOST /oauth/authorize/callback- local user picker callback that creates an authorization codePOST /oauth/token- authorization code, refresh token, and client credentials grantsPOST /oauth/revoke- revoke access or refresh tokens
OAuth app actor config is authoritative. Apps configured with actor: user use authorization code flows. Apps configured with actor: app use the app install flow and can request client credentials tokens.
Webhooks And Inspector
webhookCreate/webhookDeletemanage local webhook subscriptionsGET /- tabbed local inspector for issues, teams, users, projects, agents, auth records, webhook subscriptions, and deliveries
Linear scope checks are relaxed by default so local tests can use simple bearer tokens or the seeded lin_test_admin token. Set linear.strict_scopes: true in seed config to require read, write, issues:create, comments:create, or admin on supported GraphQL operations.
Current Linear limits: full schema coverage, exact production rate limiting, notification inbox behavior, rich document APIs, customer APIs, initiative APIs, exact search relevance, and production agent behavior are not implemented. Agent support is a focused local-test subset.
Twilio API
Stateful Twilio REST emulation with seeded accounts, Auth Tokens, API keys, incoming phone numbers, Programmable Messaging, Messaging Services, Verify, basic Voice calls, Conversations REST resources, signed webhooks, local simulator routes, and an inspector. No real SMS, MMS, WhatsApp, email, voice, carrier, compliance, billing, or SendGrid traffic is performed.
Default local credentials:
TWILIO_ACCOUNT_SID=AC00000000000000000000000000000000
TWILIO_AUTH_TOKEN=twilio_test_auth_token
TWILIO_API_KEY=SK00000000000000000000000000000000
TWILIO_API_SECRET=twilio_test_api_secret
TWILIO_PHONE_NUMBER=+15551234567
TWILIO_VERIFY_SERVICE_SID=VA00000000000000000000000000000000REST Routes
GET /2010-04-01/Accounts/{AccountSid}.json- fetch accountGET /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json- list phone numbersPOST /2010-04-01/Accounts/{AccountSid}/Messages.json- create outbound messageGET /2010-04-01/Accounts/{AccountSid}/Messages.json- list messagesPOST /2010-04-01/Accounts/{AccountSid}/Calls.json- create outbound callPOST /messaging/v1/Services- create Messaging ServicePOST /verify/v2/Services/{ServiceSid}/Verifications- start verificationPOST /verify/v2/Services/{ServiceSid}/VerificationCheck- check verification codePOST /conversations/v1/Services- create Conversation ServicePOST /conversations/v1/Services/{ServiceSid}/Conversations- create ConversationPOST /conversations/v1/Services/{ServiceSid}/Conversations/{ConversationSid}/Participants- add participantPOST /conversations/v1/Services/{ServiceSid}/Conversations/{ConversationSid}/Messages- add message
Twilio uses multiple product hosts. For local SDK tests, rewrite Twilio SDK requests to the emulator and map messaging.twilio.com to /messaging, verify.twilio.com to /verify, and conversations.twilio.com to /conversations.
SMS And OTP Testing
For the common SMS verification loop, the seeded Verify Service uses code 123456. Start a verification through the normal Verify API, then either submit 123456 in your app test or fetch the latest local code with the authenticated helper route:
curl -u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
"http://localhost:4000/_twilio/simulate/verification-code?To=%2B15550002222&ServiceSid=$TWILIO_VERIFY_SERVICE_SID"The helper returns the latest local verification for that phone number, including verification_sid, status, attempts, and code. It is local-only test support and is not part of Twilio's production API. The Verify inspector also shows each attempted code.
To test inbound SMS webhooks, configure a seeded phone number sms_url, then call POST /_twilio/simulate/inbound-message with To, From, and Body. If the destination number is assigned to a Messaging Service with inbound_request_url, the simulator sends the inbound webhook there and includes MessagingServiceSid; otherwise it uses the phone number sms_url. To test outbound delivery transitions, create a message with StatusCallback, then call POST /_twilio/simulate/message-status.
Simulator And Inspector
POST /_twilio/simulate/inbound-message- create an inbound message and invoke the configured SMS webhookPOST /_twilio/simulate/message-status- advance message status and send status callbacksGET /_twilio/simulate/verification-code- fetch the latest local Verify code byVerificationSidorToPOST /_twilio/simulate/inbound-call- create an inbound call and invoke the configured voice webhookPOST /_twilio/simulate/call-status- advance call statusPOST /_twilio/simulate/verification-status- force a verification state byVerificationSidorToGET /- tabbed inspector for messages, Verify, calls, Conversations, phone numbers, services, auth, and webhook deliveries
Current Twilio limits: no carrier delivery, A2P 10DLC, toll-free verification, real phone number purchasing, exact rate limits, Studio, Flex, TaskRouter, Video, Sync, Segment, SendGrid, Conversations SDK websocket behavior, or complete TwiML interpreter.
Apple Sign In
Sign in with Apple emulation with authorization code flow, PKCE support, RS256 ID tokens, and OIDC discovery.
GET /.well-known/openid-configuration- OIDC discovery documentGET /auth/keys- JSON Web Key Set (JWKS)GET /auth/authorize- authorization endpoint (shows user picker)POST /auth/token- token exchange (authorization code and refresh token grants)POST /auth/revoke- token revocation
Microsoft Entra ID
Microsoft Entra ID (Azure AD) v2.0 OAuth 2.0 and OpenID Connect emulation with authorization code flow, PKCE, client credentials, client-bound refresh tokens, RS256 ID tokens, and OIDC discovery.
GET /.well-known/openid-configuration- OIDC discovery documentGET /:tenant/v2.0/.well-known/openid-configuration- tenant-scoped OIDC discoveryGET /discovery/v2.0/keys- JSON Web Key Set (JWKS)GET /oauth2/v2.0/authorize- authorization endpoint (shows user picker)POST /oauth2/v2.0/token- token exchange (authorization code, refresh token, client credentials)GET /oidc/userinfo- OpenID Connect user infoGET /v1.0/me- Microsoft Graph user profileGET /oauth2/v2.0/logout- end session / logoutPOST /oauth2/v2.0/revoke- token revocation
Refresh token requests must include the client_id and client_secret of the client that received the token. Refresh tokens rotate after successful use. Legacy refresh records without a stored client binding remain supported.
curl -X POST http://localhost:4005/oauth2/v2.0/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "refresh_token=r_microsoft_...&\
client_id=example-client-id&\
client_secret=example-client-secret&\
grant_type=refresh_token"AWS
S3, SQS, IAM, and STS emulation with AWS SDK-compatible S3 paths and query-style SQS/IAM/STS endpoints. S3 uploads and downloads preserve arbitrary binary payloads, including raw byte lengths and ETags. All responses use AWS-compatible XML.
S3
S3 routes use root paths matching the real AWS S3 wire format, so the official AWS SDK works out of the box with forcePathStyle: true. Legacy /s3/ prefixed paths are also supported for backward compatibility.
GET /- list all bucketsPUT /:bucket- create bucketDELETE /:bucket- delete bucketHEAD /:bucket- check existenceGET /:bucket- list objects (prefix, delimiter, max-keys, continuation-token, start-after)POST /:bucket- presigned POST upload (browser-style multipart form with policy validation)PUT /:bucket/:key- put object (supports copy viax-amz-copy-source)GET /:bucket/:key- get objectHEAD /:bucket/:key- head objectDELETE /:bucket/:key- delete object
SQS
All operations via POST /sqs/ with Action parameter:
CreateQueue,ListQueues,GetQueueUrl,GetQueueAttributesSendMessage,ReceiveMessage,DeleteMessagePurgeQueue,DeleteQueue
IAM
All operations via POST /iam/ with Action parameter:
CreateUser,GetUser,ListUsers,DeleteUserCreateAccessKey,ListAccessKeys,DeleteAccessKeyCreateRole,GetRole,ListRoles,DeleteRole
STS
All operations via POST /sts/ with Action parameter:
GetCallerIdentity,AssumeRole
Next.js Integration
Embed emulators directly in your Next.js app so they run on the same origin. This solves the Vercel preview deployment problem where OAuth callback URLs change with every deployment.
Install
npm install @emulators/adapter-next @emulators/github @emulators/googleOnly install the emulators you need. Each @emulators/* package is published independently.
Route handler
Create a catch-all route that serves emulator traffic:
// app/emulate/[...path]/route.ts
import { createEmulateHandler } from '@emulators/adapter-next'
import * as github from '@emulators/github'
import * as google from '@emulators/google'
export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({
services: {
github: {
emulator: github,
seed: {
users: [{ login: 'octocat', name: 'The Octocat' }],
repos: [{ owner: 'octocat', name: 'hello-world', auto_init: true }],
},
},
google: {
emulator: google,
seed: {
users: [{ email: '[email protected]', name: 'Test User' }],
},
},
},
})GitHub App seeds may omit private_key. Retain the handler to call server-only generatedSecrets(); explicit keys are excluded. Persisted snapshots contain generated keys, so keep the backend private.
Auth.js / NextAuth configuration
Point your provider at the emulator paths on the same origin:
import GitHub from 'next-auth/providers/github'
const baseUrl = process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: 'http://localhost:3000'
GitHub({
clientId: 'any-value',
clientSecret: 'any-value',
authorization: { url: `${baseUrl}/emulate/github/login/oauth/authorize` },
token: { url: `${baseUrl}/emulate/github/login/oauth/access_token` },
userinfo: { url: `${baseUrl}/emulate/github/user` },
})No oauth_apps need to be seeded. When none are configured, the emulator skips client_id, client_secret, and redirect_uri validation.
Font files in serverless
Emulator UI pages use bundled fonts. Wrap your Next.js config to include them in the serverless trace:
// next.config.mjs
import { withEmulate } from '@emulators/adapter-next'
export default withEmulate({
// your normal Next.js config
})If you mount the catch-all at a custom path, pass the matching prefix:
export default withEmulate(nextConfig, { routePrefix: '/api/emulate' })Persistence
By default, emulator state is in-memory and resets on every cold start. To persist state across restarts, pass a persistence adapter:
import { createEmulateHandler } from '@emulators/adapter-next'
import * as github from '@emulators/github'
const kvAdapter = {
async load() { return await kv.get('emulate-state') },
async save(data: string) { await kv.set('emulate-state', data) },
}
export const { GET, POST, PUT, PATCH, DELETE } = createEmulateHandler({
services: { github: { emulator: github } },
persistence: kvAdapter,
})For local development, @emulators/core ships filePersistence:
import { filePersistence } from '@emulators/core'
// ...
persistence: filePersistence('.emulate/state.json'),The persistence adapter loads on cold start and saves after mutations. Generated identities also require atomic create-or-read initialize; see @emulators/core.
Nuxt Integration
Embed emulators directly in your Nuxt app so they run on the same origin. This gives OAuth flows stable callback URLs in local and preview deployments.
Install
npm install @emulators/adapter-nuxt @emulators/github @emulators/googleOnly install the emulators you need. Each @emulators/* package is published independently.
Server route
Create a named catch-all route that serves emulator traffic:
// server/routes/emulate/[...path].ts
import { createEmulateHandler } from '@emulators/adapter-nuxt'
import * as github from '@emulators/github'
import * as google from '@emulators/google'
export default defineEventHandler(createEmulateHandler({
services: {
github: {
emulator: github,
seed: {
users: [{ login: 'octocat', name: 'The Octocat' }],
repos: [{ owner: 'octocat', name: 'hello-world', auto_init: true }],
},
},
google: {
emulator: google,
seed: {
users: [{ email: '[email protected]', name: 'Test User' }],
},
},
},
}))GitHub App seeds may omit private_key. Retain the handler to call server-only generatedSecrets(); explicit keys are excluded. Persisted snapshots contain generated keys, so keep the backend private.
Nuxt config
Emulator UI pages use bundled fonts. Wrap your Nuxt config so Nitro traces the core package assets into production builds:
// nuxt.config.ts
import { withEmulate } from '@emulators/adapter-nuxt'
export default defineNuxtConfig(withEmulate({
// your normal Nuxt config
}))OAuth configuration
Point your OAuth provider at the emulator paths on the same origin:
const baseUrl = process.env.NUXT_PUBLIC_SITE_URL ?? 'http://localhost:3000'
export const githubOAuth = {
clientId: 'any-value',
clientSecret: 'any-value',
authorizationUrl: `${baseUrl}/emulate/github/login/oauth/authorize`,
tokenUrl: `${baseUrl}/emulate/github/login/oauth/access_token`,
userInfoUrl: `${baseUrl}/emulate/github/user`,
}No oauth_apps need to be seeded. When none are configured, the emulator skips client_id, client_secret, and redirect_uri validation.
Persistence
By default, emulator state is in-memory and resets on every cold start. To persist state across restarts, pass a persistence adapter:
import { createEmulateHandler } from '@emulators/adapter-nuxt'
import * as github from '@emulators/github'
const storageAdapter = {
async load() { return await useStorage('emulate').getItem<string>('state') },
async save(data: string) { await useStorage('emulate').setItem('state', data) },
}
export default defineEventHandler(createEmulateHandler({
services: { github: { emulator: github } },
persistence: storageAdapter,
}))The persistence adapter loads on cold start and saves after mutations. Generated identities also require atomic create-or-read initialize; see @emulators/core.
Architecture
packages/
emulate/ # CLI entry point (commander)
@emulators/
core/ # HTTP server, in-memory store, plugin interface, middleware
adapter-next/ # Next.js App Router integration
adapter-nuxt/ # Nuxt server route integration
vercel/ # Vercel API service
github/ # GitHub API service
google/ # Google OAuth 2.0 / OIDC + Gmail, Calendar, Drive
slack/ # Slack Web API, OAuth v2, incoming webhooks
linear/ # Linear GraphQL API, OAuth, webhooks
twilio/ # Twilio Messaging, Verify, Voice, webhooks
apple/ # Apple Sign In / OIDC
microsoft/ # Microsoft Entra ID OAuth 2.0 / OIDC + Graph /me
aws/ # AWS S3, SQS, IAM, STS
apps/
web/ # Documentation site (Next.js)The core provides a generic Store with typed Collection<T> instances supporting CRUD, indexing, filtering, and pagination. Each service plugin registers its routes with the shared internal app and uses the store for state.
Auth
Tokens are configured in the seed config and map to users. Pass them as Authorization: Bearer <token> or Authorization: token <token>.
Vercel: All endpoints accept teamId or slug query params for team scoping. Pagination uses cursor-based limit/since/until with pagination response objects.
GitHub: Public repo endpoints work without auth. Private repos and write operations require a valid token. Pagination uses page/per_page with Link headers.
Google: Standard OAuth 2.0 authorization code flow with RS256-signed OIDC ID tokens. The discovery document advertises RS256 and /oauth2/v3/certs returns the RSA public key used to verify issued tokens. Configure clients in the seed config.
Slack: All Web API endpoints require Authorization: Bearer <token>. Seeded OAuth apps create local installation records, and OAuth v2 flow with user picker UI creates scoped bot tokens. Optional strict scope mode returns missing_scope when a token lacks a required method scope.
Linear: GraphQL accepts Authorization: Bearer <token> or a bare personal API key value. Seeded Linear tokens map to users or app actors, OAuth apps support local authorization code and client credentials flows, and optional strict scope mode checks supported GraphQL operations.
Twilio: HTTP Basic auth accepts the seeded Account SID/Auth Token pair or API Key/API Secret pair. Product-host APIs are exposed under local prefixes such as /messaging/v1 and /verify/v2; the 2010 API lives at /2010-04-01.
Apple: OIDC authorization code flow with RS256 ID tokens. On first auth per user/client pair, a user JSON blob is included.
Microsoft: OIDC authorization code flow with PKCE support. Also supports client credentials grants. Microsoft Graph /v1.0/me available.
AWS: Bearer tokens or IAM access key credentials. Default key pair always seeded: AKIAIOSFODNN7EXAMPLE / wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY.
