Klaviyo ExecModule
Overview
KlaviyoModule connects ValkyrAI workflows to Klaviyo's stable Profiles, Lists, Events, and Metrics APIs. It exposes 13 bounded operations through the native map I/O ExecModule ABI and keeps the private API key in a READY IntegrationAccount.
The module fixes all production calls to https://a.klaviyo.com/api, sends JSON:API requests with revision 2026-07-15, validates IDs, filters, cursors, event/profile/list payloads, caps requests and responses at 5 MiB, returns one cursor page per execution, retries transient failures only for safe GET operations, verifies response identities, and redacts credentials from failures.
n8n's Klaviyo integration currently uses its generic HTTP Request node, which informed the broad HTTP/auth workflow reference. ValkyrAI provides a native typed connector with IntegrationAccount isolation, fixed-host enforcement, explicit operations, bounded JSON:API envelopes, write-reconciliation semantics, normalized outputs, and metadata discovery.
Usage
- Create a Klaviyo private API key with only the scopes required by the selected operations.
- Store the key only in the encrypted
apiKeyfield of a ValkyrAIIntegrationAccount, set the account toREADY, and bind it throughExecModuleConfig.authConfig.integrationAccount. - Choose one operation and provide its conditional identifiers or payload.
- For list operations, pass
nextCursorinto a later execution whenhasMoreistrue. - Give every
create_eventcall a durable caller-owneduniqueId; reuse that same value only when reconciling the same logical event. - Set
confirmRemove: trueonly after reviewing the exact list/profile relationship to remove.
Inputs
| Name | Type | Required | Description | Constraints |
|---|---|---|---|---|
operation | string | Yes | Operation listed below. | Exact allowlist of 13 values. |
profileId | string | Conditional | Klaviyo profile ID. | 1–128 letters, digits, _, or -; required by profile get/update and membership operations. |
listId | string | Conditional | Klaviyo list ID. | Same bounded identifier contract; required by list get/update and membership operations. |
record | object | Conditional | Profile or list attributes. | Non-empty allowlist; at most 50 profile fields or exactly the list name; recursive credential-like fields rejected. |
event | object | Conditional | Custom-event definition. | Requires metricName, profile, properties, and uniqueId; at most eight top-level fields and 5 MiB serialized. |
filterField | string | No | Exact-match list filter field. | Profiles: email, phone_number, external_id; events: profile_id, metric_id. |
filterValue | string | Conditional | Value paired with filterField. | At most 320 characters; control characters rejected. |
cursor | string | No | Opaque cursor from nextCursor. | 1–2,048 bounded opaque characters; never treated as a URL. |
pageSize | integer | No | Provider page size. | 1–100; default 20. |
confirmRemove | boolean | Conditional | Guard for remove_profile_from_list. | Must be exactly true. |
Profile attributes allow email, phone_number, external_id, first_name, last_name, organization, locale, title, image, and properties. create_profile requires at least one of email, phone_number, or external_id. Custom event profile identifiers allow id, email, phone_number, and external_id; use only identifiers known to describe the same profile.
The event object supports:
| Field | Required | Description |
|---|---|---|
metricName | Yes | Existing or new Klaviyo metric name, up to 255 characters. |
profile | Yes | One to four known profile identifiers. |
properties | Yes | Event properties object with at most 400 top-level fields. |
uniqueId | Yes | Caller-owned deduplication identifier, 1–255 characters. |
time | No | Provider-supported event timestamp; include it for historical data. |
value | No | Numeric event value. |
backfill | No | Set true for historical imports that must not trigger live flows. |
Outputs
| Name | Type | When present | Description |
|---|---|---|---|
status | string | Always | success or error. |
operation | string | Always | Normalized operation. |
resource | string | After validation | profiles, lists, events, metrics, or list-profiles. |
data | object | Single/action success | Verified JSON:API resource or normalized action receipt. |
id | string | Single-resource success | Verified provider identity. |
items | array | List success | One bounded JSON:API page. |
count | integer | List success | Items returned in this execution. |
hasMore | boolean | List success | Whether a trusted next link supplied another cursor. |
nextCursor | string | When another page exists | Opaque cursor for the next execution. |
httpStatus | integer | Provider success | Provider HTTP status, including 202 for accepted events. |
attempts | integer | Provider call or error | HTTP attempts consumed. |
requestId | string | When supplied | Klaviyo request trace identifier. |
error | object | Failure | Redacted code, message, and retryable fields. |
Customer and event data are classified restricted. API keys, provider bodies, filters, event properties, and profile fields are not copied into progress logs.
IntegrationAccount Requirements
| Setting | Requirement |
|---|---|
| Provider | Klaviyo stable JSON:API |
| Authentication | Private API key in Authorization: Klaviyo-API-Key ... |
| API revision | 2026-07-15 |
| Status | Exactly READY |
accountName | Human-readable Klaviyo account/application identity |
apiKey | Encrypted private API key, 16–2,048 non-whitespace characters |
| Relationship | ExecModuleConfig.authConfig.integrationAccount |
Grant only the required profiles:read/write, lists:read/write, events:read/write, and metrics:read scopes. Read-only workflows should omit write scopes. Never place the key in record, event, filterValue, URLs, examples, logs, or WorkflowState.
Configuration
Illustrative persisted configuration:
{
"version": "1.0.0",
"authConfig": {
"authStrategy": 1,
"integrationAccount": "integration-account:klaviyo-lifecycle-automation"
},
"payloadConfig": {
"parameters": "{\"operation\":\"list_profiles\",\"filterField\":\"email\",\"filterValue\":\"buyer@example.com\",\"pageSize\":20}"
}
}
The relationship is symbolic. The generated IntegrationAccount record owns the encrypted key.
Operations
| Resource | Operations |
|---|---|
| Profiles | list_profiles, get_profile, create_profile, update_profile |
| Lists | list_lists, get_list, create_list, update_list |
| Membership | add_profile_to_list, remove_profile_from_list |
| Events | list_events, create_event |
| Metrics | list_metrics |
Profiles and lists use the corresponding /api/{resource} JSON:API routes. Membership operations use /api/lists/{listId}/relationships/profiles. Events use /api/events, and metrics use /api/metrics.
Bulk imports, subscriptions/consent mutations, segments, flows, templates, catalogs, reporting, campaigns, webhooks, conversations, push tokens, profile merges/suppressions, and deletion are deferred. The beta multi-channel Campaigns API is deliberately excluded from version 1.0.0.
Errors and Failure Modes
| Failure | Cause | Retry guidance |
|---|---|---|
VALIDATION_ERROR | Missing/unsafe field, invalid ID/filter/cursor/bound, untrusted next link, mismatched identity, or incompatible success shape. | Correct the request; local validation sends no provider call. Provider-shape failures need compatibility review. |
INTEGRATION_ACCOUNT_REQUIRED | No bound secure account. | Bind the intended account. |
INTEGRATION_ACCOUNT_NOT_READY | Account status is not READY. | Repair or reauthorize the account. |
KLAVIYO_HTTP_400/404 | Provider validation failure or missing resource. | Correct JSON:API shapes, IDs, filters, event profile identifiers, or metric names. |
KLAVIYO_HTTP_401/403 | Invalid key or insufficient private-key scope. | Rotate the key or grant only the missing scope. |
KLAVIYO_HTTP_409 | Resource conflict or duplicate relationship state. | Read the current resource/relationship before deciding whether to retry. |
KLAVIYO_HTTP_429 | API rate limit. | Reads use bounded retry and numeric Retry-After; writes remain single-attempt. |
KLAVIYO_HTTP_5xx / NETWORK_ERROR | Provider or transport failure. | Reads retry up to three total attempts. Reconcile mutations before manually retrying. |
RESPONSE_TOO_LARGE | Provider response exceeded 5 MiB. | Narrow the filter or page size. |
Provider errors are bounded to 500 characters and pass through key replacement plus the shared sensitive-data policy. A failed mutation may have reached Klaviyo, so the module refuses automatic replay.
Example
Record a custom event with an explicit deduplication key:
{
"operation": "create_event",
"event": {
"metricName": "Viewed Enterprise Plan",
"profile": {
"email": "buyer@example.com"
},
"properties": {
"plan": "enterprise",
"source": "pricing-page"
},
"uniqueId": "evt-20260811-001",
"backfill": false
}
}
Expected normalized result:
{
"status": "success",
"operation": "create_event",
"resource": "events",
"data": {
"accepted": true
},
"httpStatus": 202,
"attempts": 1
}
Notes
- Pagination: one execution returns one page. Reuse
nextCursor; the module never follows provider URLs or silently loops all pages. - Rate limits: Klaviyo uses endpoint-specific burst and steady limits. The module treats
429as transient, honors bounded numericRetry-After, and caps retries to three safe-read attempts. - API limits:
pageSizeis locally capped at 100, request and response JSON at 5 MiB, event properties at 400 top-level fields, and eventuniqueIdat 255 characters. Klaviyo may enforce stricter endpoint or plan limits. - Idempotency:
GETcalls are retry-safe.create_eventis logically deduplicated by the caller-owneduniqueId; other writes are single-attempt and require read-after-write reconciliation after ambiguous failures. - Destructive behavior: profile and list deletion are not exposed.
remove_profile_from_listrequiresconfirmRemove: true. List membership and event/profile changes can alter flows, segmentation, and outbound customer messaging. - Consent: creating a profile or adding it to a list does not establish lawful email/SMS consent. Subscription endpoints are intentionally deferred; workflows must use an approved consent source and preserve audit evidence.
- Historical data: set the source event
timeandbackfill: truefor historical replays so existing flows do not send duplicate live messages. - External verification: deterministic tests cover validation, fixed routing, JSON:API envelopes, key/revision headers, cursor pagination, trusted next links, typed filters, retries, write non-retry, redaction, response identities, membership guards, and annotation discovery. Live provider execution is deferred until a separately authorized Klaviyo account is supplied.
See the official Klaviyo API overview, Profiles API, Lists API, Events API, and n8n Klaviyo integration for upstream behavior and workflow reference.