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

  1. Flow A — Platform admin bootstraps a new agency
  2. Flow B — Agency admin onboards a client end to end
  3. Flow C — Agency-wide reporting across clients
  4. Flow D — AI assistant access via MCP
  5. 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-tenantsscore.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.