Migration guide
Move from the retired hosts, routes, tool names, scopes, credentials, list envelopes and field names to the current api.vyg.app contract.
Update existing integrations for the September 2026 API changes: new URLs, renamed routes and tools, a new collection error format, one list format and snake_case field names. Find each replacement below. See the changelog for dates and details.
Hosts
| Old | New | What to do |
|---|---|---|
https://mcp.vyg.app | https://api.vyg.app/mcp | Change the server URL in your MCP client and sign in again. |
https://cdp-mcp.vyg.app | https://api.vyg.app/mcp | Change the server URL. vyg_ API keys still work as bearer tokens. |
https://cdp.vyg.app read routes | https://api.vyg.app/v1 | Move reads to /v1. Collect routes stay on cdp.vyg.app. |
https://brand-api.vyg.app | https://api.vyg.app/v1 | Change the base URL. brand-api.vyg.app is being retired. |
https://cdp.vyg.app/cdp/ingest, /cdp/events, /cdp/vyg.js | Unchanged | Nothing to change. See Data collection. |
The retired MCP hosts no longer resolve. Paths on api.vyg.app/v1 are listed in the
REST API reference.
Credentials
vyg_andvyg_ba_API keys keep working onapi.vyg.appwith the scopes they carry. See API keys.vyg_live_keys are not accepted. Requests get401withlegacy_credential_not_supported. Create avyg_ba_key instead.- MCP sign-ins made against
mcp.vyg.apporcdp-mcp.vyg.appdo not carry over. Sign in again againsthttps://api.vyg.app/mcp. - Scope names:
cdp:readis not a scope. Use the granular read scopes, such ascustomers:read,commerce:readandinsights:read. See Scopes.
MCP server name
The setup guides now name the server vyg. If you added it as liverecover or cdp-mcp, remove those
entries and add it again:
claude mcp remove liverecover
claude mcp remove cdp-mcp
claude mcp add --transport http vyg https://api.vyg.app/mcpTool names
Tools were renamed on 2026-09-26 to a verb_noun pattern, and campaign tools use "campaign" instead of
"workflow". Until 2026-09-27 the old names were still accepted; they are now removed. Calling an old name
fails, so update saved prompts, scripts and per-tool approvals or allowlists (for example Claude Code's
mcp__vyg__<tool> permissions) to the current names.
| Old name | Current name |
|---|---|
get_brand_info | get_brand |
get_brand_insights | list_conversation_insights |
get_insight_conversations | list_insight_conversations |
conversation-search | search_conversations |
dashboard-stats | get_performance_stats |
list_workflows | list_campaigns |
get_workflow_details | get_campaign |
compare_workflows | compare_campaigns |
customers_list | list_customers |
customers_search | search_customers |
customers_get | get_customer |
customers_timeline | get_customer_activity |
customers_orders | list_customer_orders |
customers_subscriptions | list_customer_subscriptions |
list_traits | list_customer_fields |
insights_ltv | get_lifetime_value |
insights_rfm | get_rfm_tiers |
get_customer_segments | get_rfm_tiers |
insights_products | get_top_products |
insights_at_risk | list_at_risk_customers |
shopify_admin_graphql | query_shopify |
search_docs_chunks | search_shopify_docs |
validate_graphql_codeblocks | validate_shopify_graphql |
klaviyo_api | query_klaviyo |
klaviyo_search_docs | search_klaviyo_docs |
query_shopify and query_klaviyo are no longer listed. Use read_shopify and mutate_shopify, and
read_klaviyo and write_klaviyo, which take the same arguments.
get_customer_segments was the 2026-09-26 name of the RFM tool. It is get_rfm_tiers and is separate from saved segments.
Routes, tools and scopes renamed on 2026-09-27
Campaign reads moved from /v1/workflows to /v1/campaigns, performance stats and campaign comparison
became GET requests, and every parameter these routes and tools take is snake_case. There are no aliases:
the old paths answer 404, and an old argument or query parameter name is refused rather than ignored
(400 bad_request over REST, an invalid-arguments error from the MCP tool).
REST routes
| Old | New |
|---|---|
GET /v1/workflows (status, includeHistorical) | GET /v1/campaigns (status, include_historical) |
GET /v1/workflows/{workflowId} | GET /v1/campaigns/{campaign_id} |
POST /v1/workflows/compare (JSON body) | GET /v1/campaigns/compare?campaign_ids=&stats=&from=&to=&timezone= |
POST /v1/dashboard/stats (JSON body) | GET /v1/stats?stats=&from=&to=&compare_from=&compare_to=&campaign_id=&timezone= |
GET /v1/search/conversations | GET /v1/conversations/search |
GET /v1/customers/search?q= | GET /v1/customers/search?query= |
GET /v1/traits | GET /v1/customers/fields (same catalog parameter and response) |
GET /v1/brand/insights?dayLimit= | GET /v1/brand/insights?day_limit= |
GET /v1/brand/insights/{brandInsightId}/conversations | GET /v1/brand/insights/{insight_id}/conversations |
stats and campaign_ids are lists. Send them as repeated parameters (stats=conversations&stats=aov) or
as one comma-separated value (stats=conversations,aov). The OpenAPI operation ids changed with the routes:
campaigns.list, campaigns.get, campaigns.compare, stats.get, conversations.search and
customers.fields.
Tool arguments
| Tool | Old argument | New argument |
|---|---|---|
list_campaigns | includeHistorical | include_historical |
get_campaign | workflowId | campaign_id |
compare_campaigns | workflowIds | campaign_ids |
get_performance_stats | workflowId, compareFrom, compareTo | campaign_id, compare_from, compare_to |
list_conversation_insights | dayLimit | day_limit |
list_insight_conversations | brandInsightId | insight_id |
get_conversation_details | conversationId | conversation_id |
search_customers | q | query |
Response fields did not change in this rename. They became snake_case on 2026-09-28; see Envelopes and field names changed on 2026-09-28.
Scopes
| Old | New |
|---|---|
workflows:read | campaigns:read: campaign reads, message variants, and campaign building options (which also accept campaigns:write) |
dashboard:read | stats:read: performance stats |
Sign-ins made before the rename keep their access: a token that carries workflows:read or
dashboard:read is read as campaigns:read or stats:read, and refreshing it returns the new names. New
sign-ins should ask for the new names; the old ones are no longer advertised. New VYG API keys can also carry these scopes. See API keys and Scopes.
Collect error envelope on 2026-09-28
Errors from the collect endpoints on https://cdp.vyg.app (POST /cdp/events and POST /cdp/ingest) now
use the REST API format, {"error": {"code", "message"}}, instead of the flat
{"error": "<code>", "error_description": "..."}. Read the code from error.code and the text from
error.message. Success responses are unchanged. Error responses with the envelope also carry an
X-Request-Id header. Some responses (unserved methods or paths, rate limits and temporary failures) use a {"message": "..."} body, so check the status and read error.code only when
the body has an error object.
Old status and error | New status and code |
|---|---|
400 bad_request | 400 bad_request |
400 batch_too_large | 400 bad_request |
400 scope_binding (with rejected) | 400 bad_request (with rejected next to error) |
401 unauthorized | 401 unauthorized |
403 forbidden (also code: beta_not_enabled) | 403 insufficient_scope |
413 payload_too_large | 413 bad_request |
500 internal_error | 500 internal |
503 service_unavailable | 503 unavailable |
503 service_unavailable_partial (accepted, failed) | 503 unavailable (accepted and failed next to error) |
A 503 that carries failed is the old service_unavailable_partial: resend only the events at those
indexes. See Collect errors.
Envelopes and field names changed on 2026-09-28
Every list now returns the same envelope, every single record is returned bare, offset paging is replaced
by opaque cursors, and every request and response field is snake_case. This covers query parameters, path
parameters, request bodies, MCP tool arguments, response keys and the list envelope itself. There are no
aliases: an old query parameter, body field or tool argument is refused (400 bad_request over REST, an
invalid-arguments error from the MCP tool), and responses carry only the new names. The error envelope,
{ "error": { "code", "message" } }, is unchanged.
List envelope
Every list returns { data, next_cursor }. next_cursor is null on the last page and always null
on a list that returns everything in one page. See Pagination.
| Endpoint | Old envelope | New envelope |
|---|---|---|
| Conversations, messages, contacts, customers, segment history | { data, nextCursor } | { data, next_cursor } |
| Segment members | { data, nextCursor, generation } | { data, next_cursor, generation } |
GET /v1/commerce/orders, GET /v1/commerce/subscriptions | { list, totalSize, offset, pageSize, scope, nextCursor } | { data, next_cursor } |
GET /v1/insights/at-risk | { list, totalSize, offset, pageSize, scope, nextCursor } | { data, next_cursor } |
GET /v1/commerce/products | { list, pageSize, scope, nextCursor } | { data, next_cursor } |
GET /v1/integrations | { list, scope } | { data, next_cursor: null } |
GET /v1/event-types | { eventTypes, scope } | { data, next_cursor: null } |
GET /v1/customers/fields | { properties, catalogVersion } | { data, next_cursor: null, catalog_version } |
GET /v1/conversations/search | { query, filters, results, totalFound } | { data, next_cursor: null } |
GET /v1/brand/insights | { insights, totalCount } | { data, next_cursor } |
GET /v1/brand/insights/{insight_id}/conversations | { brandInsightId, conversations, totalCount, limit, offset } | { data, next_cursor } |
GET /v1/campaigns | { workflows, totalCount } | { data, next_cursor: null } |
| Providers, event definitions, keys | { data } | { data, next_cursor: null } |
GET /v1/segments | { data } | { data, next_cursor } |
Customer search (GET /v1/customers/search) returns one page, so its next_cursor is always null.
catalog_version is "fields-v1" when you ask for catalog=fields-v1, otherwise null. The dropped
envelope keys (totalSize, pageSize, offset, scope, totalCount, totalFound and limit) have no
replacement; count the rows you read if you need a total.
Offset paging replaced by cursors
The offset parameter is removed; sending it returns 400 bad_request. Pass the next_cursor of the previous
page as cursor, and stop when it is null. Orders, subscriptions and customers at risk still stop at row
10,000: that page has next_cursor: null, and a cursor at or past row 10,000 returns 400.
| Endpoint or tool | Old parameters | New parameters |
|---|---|---|
GET /v1/commerce/orders, list_orders | limit, offset | limit, cursor |
GET /v1/commerce/subscriptions, list_subscriptions | limit, offset | limit, cursor |
GET /v1/insights/at-risk, list_at_risk_customers | limit, offset | limit, cursor |
GET /v1/brand/insights/{insight_id}/conversations, list_insight_conversations | limit, offset | limit, cursor |
GET /v1/brand/insights, list_conversation_insights | limit | limit, cursor (day_limit as before) |
Each endpoint keeps its other filters. Orders take created_at_gte, created_at_lte, state,
billing_status, customer_email, customer_phone and customer_id; at-risk customers take as_of.
Single records are no longer wrapped
These operations returned { "data": { ... } } and now return the object itself:
| Operation | Route |
|---|---|
| Get a conversation | GET /v1/conversations/{id} |
| Get a contact | GET /v1/contacts/{id} |
| Get, create and update a provider | GET, PATCH /v1/providers/{provider_id}, POST /v1/providers |
| Get, create and update an event definition | /v1/providers/{provider_id}/event-definitions/{definition_id} and POST /v1/providers/{provider_id}/event-definitions |
| Create, rotate and revoke a key | POST /v1/keys, POST /v1/keys/{id}/rotate, DELETE /v1/keys/{id} |
Replace response.data.id with response.id for these. Other single reads, such as a customer, an order,
a segment or a report, were already bare.
Request parameters
| Where | Old | New |
|---|---|---|
| Provider path parameter | /v1/providers/{providerId} | /v1/providers/{provider_id} |
| Event definition path parameter | .../event-definitions/{definitionId} | .../event-definitions/{definition_id} |
/v1/keys query parameter and body field | keyClass | key_class (values cdp, brand-api unchanged) |
POST /v1/keys body | firstKey | first_key |
GET /v1/customers?sort=, list_customers | totalSpend (default), ordersCount, createdAt, lastOrderAt, lastEventAt | total_spend (default), orders_count, created_at, last_order_at, last_event_at |
PATCH /v1/providers/{provider_id} body | isEnabled | is_enabled |
| Event definition create and update body | payloadSchema, identifierMappings, isEnabled, schemaVersion | payload_schema, identifier_mappings, is_enabled, schema_version |
Tool arguments renamed on 2026-09-28
| Tool | Old argument | New argument |
|---|---|---|
read_shopify, mutate_shopify, query_shopify | apiVersion | api_version |
learn_shopify_api, search_shopify_docs, validate_shopify_graphql | conversationId | conversation_id |
validate_shopify_graphql | codeblocks[].artifactId | codeblocks[].artifact_id |
list_orders, list_subscriptions, list_at_risk_customers, list_insight_conversations | offset | cursor |
Field names
Timestamps and ids that appear on many resources change the same way everywhere:
| Old | New |
|---|---|
createdAt | created_at |
updatedAt | updated_at |
brandId | brand_id |
The tables below list the other renamed fields by resource. A field that is not listed kept its name.
Conversations, messages and contacts
| Old | New |
|---|---|
lastEngagedAt | last_engaged_at |
lastReceivedAt | last_received_at |
awaitingReply | awaiting_reply |
cartValue | cart_value |
cartCurrency | cart_currency |
checkoutState | checkout_state |
lastMessage | last_message |
sentAt | sent_at |
scheduledFor | scheduled_for |
givenName | given_name |
familyName | family_name |
acceptsSmsMarketing | accepts_sms_marketing |
Conversation search results (GET /v1/conversations/search, search_conversations):
| Old | New |
|---|---|
conversationId | conversation_id |
textContent | text_content |
get_conversation_details (MCP only):
| Old | New |
|---|---|
goalStateChanges | goal_state_changes |
totalPrice | total_price |
lineItems | line_items |
discountCodes | discount_codes |
Providers and event definitions
| Old | New |
|---|---|
lookupId | lookup_id |
sourceTemplateKey | source_template_key |
isEnabled | is_enabled |
apiKey | api_key |
webhookSecret | webhook_secret |
webhookUrl | webhook_url |
providerId | provider_id |
payloadSchema | payload_schema |
identifierMappings | identifier_mappings |
schemaVersion | schema_version |
The contents of settings, payload_schema and identifier_mappings are stored as you send them and are
not renamed.
API keys
| Old | New |
|---|---|
keyPrefix | key_prefix |
lastUsedAt | last_used_at |
expiresAt | expires_at |
revokedAt | revoked_at |
plaintext on a created or rotated key is unchanged.
Customers
| Old | New |
|---|---|
ordersCount | orders_count |
totalSpend | total_spend |
totalRefunded | total_refunded |
lastOrderAt | last_order_at |
coverageFrom | coverage_from |
events30d | events_30d |
sessions30d | sessions_30d |
productViews30d | product_views_30d |
lastEventAt | last_event_at |
mergedCustomerIds | merged_customer_ids |
firstOrderAt | first_order_at |
readOnlyReason | read_only_reason |
catalogVersion | catalog_version |
Activity timeline (GET /v1/customers/{id}/timeline, get_customer_activity):
| Old | New |
|---|---|
eventKey | event_key |
occurredAt | occurred_at |
eventType | event_type |
sourceSystem | source_system |
entityKind | entity_kind |
entityKey | entity_key |
productKey | product_key |
pagePath | page_path |
pageTitle | page_title |
referrerHost | referrer_host |
utmSource | utm_source |
utmMedium | utm_medium |
utmCampaign | utm_campaign |
sessionId | session_id |
A customer's orders and subscriptions (GET /v1/customers/{id}/orders and /subscriptions,
list_customer_orders, list_customer_subscriptions):
| Old | New |
|---|---|
orderKey | order_key |
subscriptionKey | subscription_key |
financialStatus | financial_status |
fulfillmentStatus | fulfillment_status |
billingInterval | billing_interval |
nextBillingAt | next_billing_at |
lineId | line_id |
variantKey | variant_key |
unitPrice | unit_price |
sourceSystem and productKey change as in the timeline table.
Segments
| Old | New |
|---|---|
currentVersionId | current_version_id |
createdBy | created_by |
evaluationStatus | evaluation_status |
memberCount | member_count |
evaluatedAt | evaluated_at |
lastRunAt | last_run_at |
currentVersionEvaluated | current_version_evaluated |
segmentId | segment_id |
estimatedAt | estimated_at |
customerId | customer_id |
lrContactId | lr_contact_id |
joinedAt | joined_at |
currentCustomerId | current_customer_id |
changedAt | changed_at |
schemaVersion on a segment is schema_version. The segment definition is stored as you send it and
its keys are not renamed. See Segment rules.
Commerce and insights
Orders, subscriptions and at-risk rows were already snake_case; only their envelope changed. On products,
images[].altText is images[].alt_text, on the list and on GET /v1/commerce/products/{id}. The LTV,
RFM and top-products reports are unchanged and keep scope.
| Old | New |
|---|---|
altText | alt_text |
Brand, stats and conversation insights
GET /v1/brand (get_brand):
| Old | New |
|---|---|
companyWebsite | company_website |
supportEmail | support_email |
supportPhone | support_phone |
escalationEmail | escalation_email |
tosUrl | tos_url |
privacyPolicyUrl | privacy_policy_url |
faqPageUrl | faq_page_url |
reEngagePeriod | re_engage_period |
planName | plan_name |
planPrice | plan_price |
usageChargePercentage | usage_charge_percentage |
billingEmail | billing_email |
paymentProvider | payment_provider |
isEnabled and lookupId on integrations[] are is_enabled and lookup_id.
GET /v1/stats (get_performance_stats) and GET /v1/campaigns/compare (compare_campaigns):
| Old | New |
|---|---|
currentValue | current_value |
previousValue | previous_value |
comparisonChart | comparison_chart |
dateRange | date_range |
compareFrom | compare_from |
compareTo | compare_to |
get_performance_stats also returns query: the stats, campaign_id and timezone the numbers were
computed with. timezone is America/Los_Angeles when the request named none.
Conversation insights (list_conversation_insights, list_insight_conversations):
| Old | New |
|---|---|
messagePreview | message_preview |
conversationId, checkoutState and updatedAt change as in the tables above.
Campaigns
GET /v1/campaigns, GET /v1/campaigns/{campaign_id} and GET /v1/campaigns/compare (list_campaigns,
get_campaign, compare_campaigns):
| Old | New |
|---|---|
isPaused | is_paused |
startsAt | starts_at |
endsAt | ends_at |
delayMinutes | delay_minutes |
delayType | delay_type |
weekDays | week_days |
specificTime | specific_time |
nodeId | node_id |
externalId | external_id |
agentReply | agent_reply |
replyWithMessage | reply_with_message |
contactSupport | contact_support |
flowMessages | flow_messages |
knowledgeBase | knowledge_base |
compare_campaigns returns the compared campaigns under campaigns (was workflows). supportEmail
inside agent_reply is support_email. The contents of goal, flow_messages[].cart.rules
and filters[].filter.rule are not renamed. Message variants, discounts, campaign writes and campaign
building options were already snake_case.
Klaviyo documentation search
search_klaviyo_docs (MCP only):
| Old | New |
|---|---|
resultCount | result_count |
operationId | operation_id |
docUrl | doc_url |
guideTopics | guide_topics |
endpointFamilies | endpoint_families |
The payloads that read_shopify, mutate_shopify, read_klaviyo and write_klaviyo (and the unlisted
query_shopify and query_klaviyo) return come from Shopify and Klaviyo and are not renamed.
Documentation URLs
The documentation moved from /docs, /cdp-api and /mcp to root-level sections on docs.vyg.ai. Old
URLs, including those on docs.cdp.vyg.app and docs.mcp.vyg.app, redirect to the new pages.