Demo Flows
Runnable, end-to-end examples against the Vouch API and MCP server. Each flow is a sequence of real requests you can paste into a terminal (adjust the base URL and tokens for your environment) — nothing here is pseudocode.
Base URL used below: https://api.tryvouch.io for production, or
http://localhost:4000 for a local services/api dev server. Swap
$API accordingly:
export API="https://api.tryvouch.io"
Contents
- Flow A — Platform admin bootstraps a new agency
- Flow B — Agency admin onboards a client end to end
- Flow C — Agency-wide reporting across clients
- Flow D — AI assistant access via MCP
- Flow E — A single-tenant demo (no agency layer)
Flow A — Platform admin bootstraps a new agency
Run once per new agency. Requires a platform_admin bearer token.
export PLATFORM_TOKEN="<platform_admin_jwt>"
# 1. Create the agency, with a first agency_admin in the same call.
curl -s -X POST "$API/admin/agencies" \
-H "Authorization: Bearer $PLATFORM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Acme Marketing Group",
"slug": "acme-marketing",
"adminEmail": "ops@acmemarketing.com",
"adminName": "Jordan Lee"
}' | tee agency.json
export AGENCY_ID=$(jq -r .id agency.json)
echo "Agency: $AGENCY_ID"
ops@acmemarketing.com receives a password-setup email (or is provisioned
via Azure AD/B2B invite if your directory is configured) and can now sign in
and obtain an agency_admin token for Flow B.
# Optional: confirm the agency and its (currently empty) tenant list.
curl -s "$API/admin/agencies/$AGENCY_ID" \
-H "Authorization: Bearer $PLATFORM_TOKEN" | jq
Flow B — Agency admin onboards a client end to end
Everything from here runs as the agency admin — this is the flow a real agency operator repeats for every new client they sign.
export AGENCY_TOKEN="<agency_admin_jwt>"
# 1. Create the client's tenant (+ default brand, in one call).
curl -s -X POST "$API/agency/tenants" \
-H "Authorization: Bearer $AGENCY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Riverside Dental",
"slug": "riverside-dental",
"plan": "growth",
"brandName": "Riverside Dental",
"brandSlug": "riverside-dental"
}' | tee tenant.json
export TENANT_ID=$(jq -r .id tenant.json)
echo "Tenant: $TENANT_ID"
# 2. Add the client's first location.
curl -s -X POST "$API/agency/tenants/$TENANT_ID/locations" \
-H "Authorization: Bearer $AGENCY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Riverside Dental — Main St",
"addressLine1": "123 Main St",
"city": "Riverside",
"state": "CA",
"postalCode": "92501",
"phone": "+19515550100"
}' | tee location.json
export LOCATION_ID=$(jq -r .id location.json)
echo "Location: $LOCATION_ID"
# 3. Stand up the client's first campaign (a "marketing action" —
# an event-triggered post-visit review request over SMS + email).
curl -s -X POST "$API/agency/tenants/$TENANT_ID/campaigns" \
-H "Authorization: Bearer $AGENCY_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"Post-visit review request\",
\"type\": \"event_triggered\",
\"channels\": [\"sms\", \"email\"],
\"locationId\": \"$LOCATION_ID\"
}" | tee campaign.json
export CAMPAIGN_ID=$(jq -r .id campaign.json)
echo "Campaign: $CAMPAIGN_ID"
# 4. Add a staff user at the client so they can log into Vouch directly.
curl -s -X POST "$API/agency/tenants/$TENANT_ID/users" \
-H "Authorization: Bearer $AGENCY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"email": "frontdesk@riversidedental.example",
"name": "Front Desk",
"role": "tenant_user"
}' | jq
At this point the client has a tenant, a location, a running campaign, and a login for their own staff — all without the agency admin ever needing a tenant-specific credential.
Going deeper on this tenant
Everything else (message templates, sequences, the review inbox, analytics,
SSO settings, CRM integrations…) uses the regular tenant-scoped API with
x-tenant-id set to $TENANT_ID — the agency admin's bearer token already
has write access to it (see AGENCY_GUIDE.md §8):
# Bulk-import the rest of this client's locations from a CSV.
curl -s -X POST "$API/v1/locations/bulk" \
-H "Authorization: Bearer $AGENCY_TOKEN" \
-H "x-tenant-id: $TENANT_ID" \
-F "file=@riverside-locations.csv"
# Attach a message template to the campaign created above.
curl -s -X POST "$API/v1/templates" \
-H "Authorization: Bearer $AGENCY_TOKEN" \
-H "x-tenant-id: $TENANT_ID" \
-H "Content-Type: application/json" \
-d "{
\"campaignId\": \"$CAMPAIGN_ID\",
\"channel\": \"sms\",
\"locale\": \"en-US\",
\"body\": \"Thanks for visiting Riverside Dental! Mind leaving us a quick review? {{reviewLink}}\"
}"
Flow C — Agency-wide reporting across clients
Repeat Flow B for a second client (say, slug: "downtown-auto"), then pull a
rollup across both:
# Every location across every client this agency owns.
curl -s "$API/agency/locations" \
-H "Authorization: Bearer $AGENCY_TOKEN" | jq
# Every campaign across every client, scoped to one client via ?tenantId=.
curl -s "$API/agency/campaigns" \
-H "Authorization: Bearer $AGENCY_TOKEN" | jq
curl -s "$API/agency/campaigns?tenantId=$TENANT_ID" \
-H "Authorization: Bearer $AGENCY_TOKEN" | jq
# The agency's own dashboard view: every tenant with user/contact/solicitation counts.
curl -s "$API/agency/me" \
-H "Authorization: Bearer $AGENCY_TOKEN" | jq
Flow D — AI assistant access via MCP
This flow uses the MCP server instead of REST — the natural interface for
"ask an AI assistant across all my clients." It requires an OAuth token
(not an API key — see MCP_INTEGRATION.md §3.4)
issued to the same agency-admin user from Flow A/B.
# 1. OAuth dance (see docs/OAUTH.md for the full PKCE flow) — abbreviated here.
# Ends with an access token prefixed vouch_oauth_.
export OAUTH_TOKEN="vouch_oauth_..."
# 2. Open an MCP session (Streamable HTTP) and call agency.list-tenants.
# In practice you'd do this through an MCP client (Claude Desktop, Claude
# Code, a custom agent) — shown here as raw JSON-RPC over HTTP for clarity.
curl -s -X POST "$API/mcp" \
-H "Authorization: Bearer $OAUTH_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { "name": "agency.list-tenants", "arguments": {} }
}'
# → { tenants: [{ id: "cly1…", name: "Riverside Dental", … }, { id: "cly2…", name: "Downtown Auto Group", … }], count: 2 }
With a tenant id in hand, target it explicitly on any agency-aware skill — the same session, no re-authentication:
curl -s -X POST "$API/mcp" \
-H "Authorization: Bearer $OAUTH_TOKEN" \
-H "Mcp-Session-Id: <session-id-from-initialize>" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "location.list",
"arguments": { "tenantId": "cly1…" }
}
}'
curl -s -X POST "$API/mcp" \
-H "Authorization: Bearer $OAUTH_TOKEN" \
-H "Mcp-Session-Id: <session-id-from-initialize>" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "score.location",
"arguments": { "tenantId": "cly1…", "locationId": "'"$LOCATION_ID"'" }
}
}'
In a real client (Claude Desktop, Claude Code, Claude.ai, a custom agent),
you'd never construct these JSON-RPC bodies by hand — you'd ask in plain
language ("which of my clients has the lowest Vouch Score right now?") and
the model chains agency.list-tenants → score.location (once per tenant)
on your behalf.
Flow E — A single-tenant demo (no agency layer)
Not every Vouch customer is an agency client — most self-serve signups land
in the built-in "house" agency and never see any of the /agency/* surface.
For a minimal, agency-agnostic demo:
export TENANT_TOKEN="<tenant_admin_jwt>"
export TENANT_ID="<tenant_id>"
curl -s -X POST "$API/v1/locations" \
-H "Authorization: Bearer $TENANT_TOKEN" \
-H "x-tenant-id: $TENANT_ID" \
-H "Content-Type: application/json" \
-d '{ "name": "Downtown Location", "city": "Austin", "state": "TX" }'
curl -s -X POST "$API/v1/campaigns" \
-H "Authorization: Bearer $TENANT_TOKEN" \
-H "x-tenant-id: $TENANT_ID" \
-H "Content-Type: application/json" \
-d '{ "name": "Weekly review push", "type": "continuous", "channels": ["email"] }'
This is the same underlying API Flows B and E use — the difference is only
which credential and which router (/agency/* vs /v1/*) you start from.
See also: AGENCY_GUIDE.md for the narrative
version of Flows A–C, MCP_INTEGRATION.md for full
MCP client setup, and /docs/api for the interactive OpenAPI
reference covering every endpoint used above.