Customer graph tools
Read your Customer Graph from an external agent: look up a customer, search in natural language, walk a timeline, summarize a window, and list the runs that touched a customer.
The customer-graph server ships at v1 and is read-only. Point your client at its Streamable HTTP endpoint and authenticate with a token carrying at least customer.read. See the quickstart to connect.
https://mcp-customer-graph.porchops.com/mcpTools
customer.search
read-onlySearch the current app's Customer Graph. Pass a natural-language query in `q` (PorchOps translates it to filters internally) OR structured `filters` directly (skips the NL step). Returns up to 20 matching customers, freshest-active first, plus the filters that were applied. Read-only.
- qstringA natural-language search query, e.g. 'paying customers from acme.com'. Up to 500 characters. When set, PorchOps translates it into structured filters internally. Provide q OR filters (at least one).
- filtersobjectStructured filters. Provide these directly to skip the natural-language step. Provide q OR filters (at least one).
- filters.lifecycleStagesarrayMatch customers in ANY of these lifecycle stages (signup, trial, activated, paying, expanding, at_risk, churned, churned_recovered). Omit to match all stages.
- filters.emailDomainstringMatch customers whose primary email is at this domain, e.g. 'acme.com' (with or without a leading @). Case-insensitive.
- filters.mrrMinDollarsnumberOnly customers whose monthly recurring revenue is at least this many dollars.
- filters.mrrMaxDollarsnumberOnly customers whose monthly recurring revenue is at most this many dollars.
- filters.textstringFree-text substring matched (case-insensitive) across the customer's name, primary email, and company name.
- limitintegerMaximum number of matches to return. Defaults to 20; capped at 20 (top-20).
- matchesarray · requiredThe matching customers, freshest-active first (up to the requested limit).
- matches[].customerIdstring · requiredThe matched customer's PorchOps local id (cus_-prefixed).
- matches[].emailPrimarystring · requiredThe customer's primary email address.
- matches[].namestring | null · requiredThe customer's name; null when unknown.
- matches[].companyNamestring | null · requiredThe customer's company name; null when unknown.
- matches[].lifecycleStageenum · requiredThe customer's current lifecycle stage.signuptrialactivatedpayingexpandingat_riskchurnedchurned_recovered
- matches[].mrrDollarsnumber · requiredThe customer's monthly recurring revenue in dollars.
- matches[].lastActivityAtstring | null · requiredISO 8601 timestamp of the customer's last recorded activity; null when unknown.
- appliedFiltersobject · requiredThe structured filters actually applied - either the caller's filters, or the filters translated from the natural-language query. Lets a BYOA client see how its query was interpreted.
- appliedFilters.lifecycleStagesarrayMatch customers in ANY of these lifecycle stages (signup, trial, activated, paying, expanding, at_risk, churned, churned_recovered). Omit to match all stages.
- appliedFilters.emailDomainstringMatch customers whose primary email is at this domain, e.g. 'acme.com' (with or without a leading @). Case-insensitive.
- appliedFilters.mrrMinDollarsnumberOnly customers whose monthly recurring revenue is at least this many dollars.
- appliedFilters.mrrMaxDollarsnumberOnly customers whose monthly recurring revenue is at most this many dollars.
- appliedFilters.textstringFree-text substring matched (case-insensitive) across the customer's name, primary email, and company name.
- interpretedFromNaturalLanguageboolean · requiredTrue when the applied filters were translated from a natural-language q.
{
"q": "paying customers from heronsoftware.com quiet 30+ days"
}{
"matches": [
{
"customerId": "cus_01HKQM4Z8YB3T7VN2P9R6E4WQF",
"emailPrimary": "founder@heronsoftware.com",
"name": "Heron Software",
"companyName": "Heron Software",
"lifecycleStage": "signup",
"mrrDollars": 240,
"lastActivityAt": "2026-05-14T09:32:00.000Z"
}
],
"appliedFilters": {
"lifecycleStages": [
"signup"
],
"emailDomain": "heronsoftware.com",
"mrrMinDollars": 240,
"mrrMaxDollars": 240,
"text": "example"
},
"interpretedFromNaturalLanguage": false
}customer.read
read-onlyFetch one customer from the current app's Customer Graph by EITHER its PorchOps local id (cus_...), its Stripe customer id (cus_OABC...), OR its primary email. Provide exactly one key. Returns the customer's identity, lifecycle stage, value (MRR + LTV in dollars), and health signals, or customer: null when nothing matches. Read-only.
- customerIdstringThe customer's PorchOps local id (cus_-prefixed Crockford base32, e.g. cus_01HKQM4Z...). Use when you already hold the local id from a prior customer.search or customer.list. Provide exactly one of customerId, stripeCustomerId, or email.
- stripeCustomerIdstringThe customer's Stripe customer id (e.g. cus_OABC1234567890). Use when you have a Stripe id from a webhook payload or the Stripe dashboard. Provide exactly one of customerId, stripeCustomerId, or email.
- emailstringThe customer's primary email address (case-insensitive). Use when you only know the person's email. Provide exactly one of customerId, stripeCustomerId, or email.
- customerobject | null · requiredThe matched customer, or null when no non-deleted customer in this app matches the supplied key.
- customer.customerIdstring · requiredThe customer's PorchOps local id (cus_-prefixed).
- customer.emailPrimarystring · requiredThe customer's primary email address.
- customer.stripeCustomerIdstring | null · requiredThe customer's Stripe customer id; null for a manually-entered customer.
- customer.namestring | null · requiredThe customer's full name; null when unknown.
- customer.companyNamestring | null · requiredThe customer's company name; null when unknown.
- customer.lifecycleStageenum · requiredWhere the customer sits in the funnel: one of signup, trial, activated, paying, expanding, at_risk, churned, or churned_recovered.signuptrialactivatedpayingexpandingat_riskchurnedchurned_recovered
- customer.mrrDollarsnumber · requiredThe customer's monthly recurring revenue in whole and fractional dollars.
- customer.ltvDollarsnumber · requiredThe customer's lifetime value to date, in dollars.
- customer.healthScorenumber | null · requiredA 0-100 account-health score (higher is healthier); null until an agent has computed one.
- customer.lastActivityAtstring | null · requiredISO 8601 timestamp of the customer's most recent recorded activity; null when never recorded.
{
"customerId": "cus_01HKQM4Z8YB3T7VN2P9R6E4WQF"
}{
"customer": {
"customerId": "cus_01HKQM4Z8YB3T7VN2P9R6E4WQF",
"emailPrimary": "founder@heronsoftware.com",
"stripeCustomerId": "cus_OABC1234567890",
"name": "Heron Software",
"companyName": "Heron Software",
"lifecycleStage": "signup",
"mrrDollars": 240,
"ltvDollars": 5760,
"healthScore": 82,
"lastActivityAt": "2026-05-14T09:32:00.000Z"
}
}customer.timeline
read-onlyBuild one customer's activity timeline: payments (charges + invoices), support messages, agent runs, and Knowledge Base notes, merged newest-first. Each entry is tagged with its kind. Returns an empty list when the customer has no activity. Read-only.
- customerIdstring · requiredThe customer's PorchOps local id (cus_-prefixed). The timeline is built for this customer.
- limitintegerMaximum number of timeline entries to return, newest-first across all activity types. Defaults to 50; capped at 200.
- entriesarray · requiredThe customer's activity, newest-first across charges, invoices, messages, runs, and KB notes. Empty when the customer has no activity (or does not exist in this app).
{
"customerId": "cus_01HKQM4Z8YB3T7VN2P9R6E4WQF"
}{
"entries": [
{
"kind": "charge",
"at": "2026-05-14T09:32:00.000Z",
"refId": "chg_01HKQM4Z8YB3T7VN2P9R6E4WQF",
"title": "Payment recovered",
"status": "completed",
"amountCents": 1,
"currency": "usd"
}
]
}customer.activity_summary
read-onlySummarize one customer's activity over a window: charge / invoice / message / run / KB-note counts, gross charge amount, and the last-touch timestamp, plus the customer's current lifecycle stage and MRR. Window defaults to the last 30 days. Returns found: false with a zeroed shell when the customer is not in this app. Read-only.
- customerIdstring · requiredThe customer's PorchOps local id (cus_-prefixed). The summary is computed for this customer.
- windowStartstringISO 8601 datetime at the start of the window. Defaults to 30 days before windowEnd when omitted.
- windowEndstringISO 8601 datetime at the end of the window. Defaults to the current time when omitted. Must be >= windowStart; the window span must be <= 365 days.
- foundboolean · requiredTrue when the customer exists in this app; false means the rest is a zeroed shell.
- customerIdstring · requiredEcho of the requested customer id.
- windowStartstring · requiredResolved window start as an ISO 8601 datetime (UTC).
- windowEndstring · requiredResolved window end as an ISO 8601 datetime (UTC).
- lifecycleStageenum | null · requiredThe customer's CURRENT lifecycle stage (a snapshot, not a window quantity); null when the customer was not found.signuptrialactivatedpayingexpandingat_riskchurnedchurned_recovered
- mrrDollarsnumber · requiredThe customer's CURRENT monthly recurring revenue in dollars (snapshot); 0 when not found.
- lastTouchAtstring | null · requiredISO 8601 timestamp of the most recent activity within the window, across every primitive; null when the window is empty.
- chargesobject · requiredCharge activity within the window.
- charges.totalinteger · requiredCharges made in the window.
- charges.succeededinteger · requiredOf those, how many succeeded.
- charges.failedinteger · requiredOf those, how many failed.
- charges.grossAmountCentsinteger · requiredSum of charge amounts in the window, in cents (the customer's own revenue).
- invoicesobject · requiredInvoice activity within the window.
- invoices.totalinteger · requiredInvoices created in the window.
- invoices.amountPaidCentsinteger · requiredSum of amount paid across those invoices, in cents.
- messagesobject · requiredInbox (support) activity within the window.
- messages.totalinteger · requiredInbox messages received in the window.
- messages.inboundinteger · requiredOf those, how many were inbound (from the customer).
- messages.outboundinteger · requiredOf those, how many were outbound (operator replies).
- runsobject · requiredAgent-run activity within the window.
- runs.totalinteger · requiredPlaybook runs that touched the customer in the window.
- kbNotesobject · requiredKnowledge Base activity within the window.
- kbNotes.totalinteger · requiredKnowledge Base notes attached to the customer in the window.
{
"customerId": "cus_01HKQM4Z8YB3T7VN2P9R6E4WQF"
}{
"found": false,
"customerId": "cus_01HKQM4Z8YB3T7VN2P9R6E4WQF",
"windowStart": "2026-05-14T09:32:00.000Z",
"windowEnd": "2026-05-14T09:32:00.000Z",
"lifecycleStage": "signup",
"mrrDollars": 240,
"lastTouchAt": "2026-05-14T09:32:00.000Z",
"charges": {
"total": 3,
"succeeded": 1,
"failed": 1,
"grossAmountCents": 1
},
"invoices": {
"total": 3,
"amountPaidCents": 1
},
"messages": {
"total": 3,
"inbound": 1,
"outbound": 1
},
"runs": {
"total": 3
},
"kbNotes": {
"total": 3
}
}customer.related_runs
read-onlyList the playbook runs that touched one customer, newest-first, with status and cost (in PorchOps credits). Anchored on a customer id. Returns an empty list when the customer has no runs. Read-only. Requires both customer.read and agent.runs.read permissions.
- customerIdstring · requiredThe customer's PorchOps local id (cus_-prefixed). Runs anchored on this customer are returned.
- limitintegerMaximum number of runs to return, newest-first. Defaults to 20; capped at 100.
- runsarray · requiredThe playbook runs anchored on this customer, newest-first. Empty when the customer has no runs (or does not exist in this app).
- runs[].runIdstring · requiredThe run's PorchOps id (run_-prefixed).
- runs[].playbookIdstring · requiredThe id of the playbook this run executed.
- runs[].statusstring · requiredThe run's current status: one of pending, running, awaiting_approval, completed, terminated, or failed.
- runs[].startedAtstring | null · requiredISO 8601 timestamp when the run started executing; null if it never started.
- runs[].completedAtstring | null · requiredISO 8601 timestamp when the run reached a terminal state; null while non-terminal.
- runs[].costCreditsnumber · requiredThe run's cost in PorchOps credits (units of agent work). Never a raw dollar cost, token count, or model name.
{
"customerId": "cus_01HKQM4Z8YB3T7VN2P9R6E4WQF"
}{
"runs": [
{
"runId": "run_01HKQM4Z8YB3T7VN2P9R6E4WQF",
"playbookId": "plb_01HKQM4Z8YB3T7VN2P9R6E4WQF",
"status": "completed",
"startedAt": "2026-05-14T09:32:00.000Z",
"completedAt": "2026-05-14T09:32:00.000Z",
"costCredits": 1
}
]
}