-
Notifications
You must be signed in to change notification settings - Fork 306
feat(klaviyo): Upgrade API to 2026-01-15 with feature flag #3707
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
harsh-joshi99
wants to merge
4
commits into
main
Choose a base branch
from
klaviyo-api-2026-01-15
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
3effb0d
feat(klaviyo): upgrade API to 2026-01-15 behind feature flag
harsh-joshi99 6568ea0
chore: add api-version-upgrade skill to repo
harsh-joshi99 f28d18f
fix(klaviyo): fix import order and rename feature flag to match conve…
harsh-joshi99 dbbcd4d
feat(klaviyo): add real API validation harness for version upgrades
harsh-joshi99 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| { | ||
| "skill_name": "api-version-upgrade", | ||
| "evals": [ | ||
| { | ||
| "id": 1, | ||
| "name": "klaviyo-simple-to-canary-migration", | ||
| "prompt": "Upgrade Klaviyo destination from version 2025-01-15 to 2026-01-15. The destination currently uses a simple REVISION_DATE constant in config.ts without feature flags. Please migrate it to use the canary pattern with feature flags, analyze breaking changes from the Klaviyo API changelog (https://developers.klaviyo.com/en/docs/changelog), run all tests, and create a PR. The Klaviyo API uses the revision date as an HTTP header.", | ||
| "expected_output": "Complete upgrade with:\n- versioning-info.ts created with both versions\n- config.ts updated to import from versioning-info\n- buildHeaders() updated to accept features parameter\n- getApiRevision() helper function created\n- extendRequest updated to pass features\n- All tests passing for both versions\n- Breaking changes analysis document\n- PR created with detailed description\n- Branch pushed to origin", | ||
| "files": [], | ||
| "assertions": [] | ||
| }, | ||
| { | ||
| "id": 2, | ||
| "name": "google-cm360-canary-update", | ||
| "prompt": "hey can you bump Google Campaign Manager 360 from v4 to v5? they already have the canary pattern set up with versioning-info.ts and feature flags. just need to update the CANARY version from v5 to v6 and check for breaking changes. their changelog is at https://developers.google.com/doubleclick-advertisers/deprecation. run tests and push a pr when done", | ||
| "expected_output": "Complete upgrade with:\n- GOOGLE_CM360_CANARY_API_VERSION updated to 'v6'\n- GOOGLE_CM360_API_VERSION remains 'v4' (stable)\n- Breaking changes fetched from Google docs\n- Comprehensive analysis of v4→v6 changes\n- All tests passing (stable and canary)\n- Feature flag tests verified\n- PR created with breaking changes\n- Branch created and pushed", | ||
| "files": [], | ||
| "assertions": [] | ||
| }, | ||
| { | ||
| "id": 3, | ||
| "name": "new-destination-first-version", | ||
| "prompt": "We're adding a new destination called 'amplitude-web' that uses API version 'v2' from https://amplitude.com/api/v2. It currently has the version hardcoded in utils.ts as a constant. Can you set up the versioning structure properly with v1 as stable and v2 as canary behind a feature flag? Also check https://amplitude.com/docs/apis/api-changelog for breaking changes between v1 and v2.", | ||
| "expected_output": "Complete setup with:\n- versioning-info.ts created from scratch\n- AMPLITUDE_WEB_API_VERSION = 'v1'\n- AMPLITUDE_WEB_CANARY_API_VERSION = 'v2'\n- getApiVersion() helper created\n- FLAGON_NAME defined\n- All API call sites updated to use helper\n- Tests added for both versions\n- Breaking changes analysis\n- PR with full implementation", | ||
| "files": [], | ||
| "assertions": [] | ||
| } | ||
| ] | ||
| } |
334 changes: 334 additions & 0 deletions
334
.claude/skills/api-version-upgrade/references/common-patterns.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,334 @@ | ||
| # Common API Versioning Patterns in Action Destinations | ||
|
|
||
| This document catalogs the different patterns used for API versioning across Segment Action Destinations. | ||
|
|
||
| ## Pattern 1: Canary with versioning-info.ts (Preferred) | ||
|
|
||
| **Used by**: Google Campaign Manager 360, The Trade Desk CRM | ||
|
|
||
| **Structure**: | ||
|
|
||
| ``` | ||
| destination/ | ||
| ├── versioning-info.ts # Version constants | ||
| ├── utils.ts # Helper with getApiVersion() | ||
| └── actions/ | ||
| └── someAction/ | ||
| └── index.ts # Uses getApiVersion(features) | ||
| ``` | ||
|
|
||
| **versioning-info.ts**: | ||
|
|
||
| ```typescript | ||
| /** DESTINATION_API_VERSION | ||
| * Destination API version (stable/production). | ||
| * API reference: https://... | ||
| */ | ||
| export const DESTINATION_API_VERSION = 'v4' | ||
|
|
||
| /** DESTINATION_CANARY_API_VERSION | ||
| * Destination API version (canary/feature-flagged). | ||
| */ | ||
| export const DESTINATION_CANARY_API_VERSION = 'v5' | ||
| ``` | ||
|
|
||
| **utils.ts** (or functions.ts): | ||
|
|
||
| ```typescript | ||
| import { Features } from '@segment/actions-core' | ||
| import { DESTINATION_API_VERSION, DESTINATION_CANARY_API_VERSION } from './versioning-info' | ||
|
|
||
| export const API_VERSION = DESTINATION_API_VERSION | ||
| export const CANARY_API_VERSION = DESTINATION_CANARY_API_VERSION | ||
| export const FLAGON_NAME = 'destination-canary-api-version' | ||
|
|
||
| export function getApiVersion(features?: Features): string { | ||
| return features && features[FLAGON_NAME] ? CANARY_API_VERSION : API_VERSION | ||
| } | ||
|
|
||
| // Usage in API calls | ||
| export async function send(request, settings, payload, features) { | ||
| const version = getApiVersion(features) | ||
| const response = await request(`${BASE_URL}/${version}/endpoint`, { | ||
| method: 'POST', | ||
| json: payload | ||
| }) | ||
| return response | ||
| } | ||
| ``` | ||
|
|
||
| **index.ts** (destination definition): | ||
|
|
||
| ```typescript | ||
| extendRequest({ settings, features }) { | ||
| return { | ||
| headers: buildHeaders(settings.api_key, features) | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| **Advantages**: | ||
|
|
||
| - Clean separation of version config | ||
| - Feature flag allows safe testing | ||
| - Easy to promote canary to stable | ||
| - Consistent pattern across destinations | ||
| - Gradual rollout capability | ||
|
|
||
| ## Pattern 2: Simple Constant in config.ts | ||
|
|
||
| **Used by**: Klaviyo (before migration) | ||
|
|
||
| **Structure**: | ||
|
|
||
| ``` | ||
| destination/ | ||
| ├── config.ts # Version constant | ||
| ├── functions.ts # Uses version in buildHeaders() | ||
| └── actions/ | ||
| └── someAction/ | ||
| └── index.ts # Uses buildHeaders() | ||
| ``` | ||
|
|
||
| **config.ts**: | ||
|
|
||
| ```typescript | ||
| export const API_URL = 'https://a.klaviyo.com/api' | ||
| export const REVISION_DATE = '2025-01-15' | ||
| ``` | ||
|
|
||
| **functions.ts**: | ||
|
|
||
| ```typescript | ||
| import { API_URL, REVISION_DATE } from './config' | ||
|
|
||
| export function buildHeaders(authKey: string) { | ||
| return { | ||
| Authorization: `Klaviyo-API-Key ${authKey}`, | ||
| Accept: 'application/json', | ||
| revision: REVISION_DATE, // ← Used as HTTP header | ||
| 'Content-Type': 'application/json' | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| **index.ts**: | ||
|
|
||
| ```typescript | ||
| extendRequest({ settings }) { | ||
| return { | ||
| headers: buildHeaders(settings.api_key) | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| **Limitations**: | ||
|
|
||
| - No feature flag support | ||
| - No canary testing capability | ||
| - Direct change to production version | ||
| - Harder to roll back if issues arise | ||
|
|
||
| **Migration path**: Convert to Pattern 1 | ||
|
|
||
| ## Pattern 3: Inline Constants | ||
|
|
||
| **Used by**: Some older destinations | ||
|
|
||
| **Structure**: | ||
|
|
||
| ```typescript | ||
| // Directly in utils.ts or functions.ts | ||
| const API_VERSION = 'v3' | ||
| const BASE_URL = `https://api.example.com/${API_VERSION}` | ||
|
|
||
| export async function sendData(request, payload) { | ||
| return await request(`${BASE_URL}/endpoint`, { | ||
| method: 'POST', | ||
| json: payload | ||
| }) | ||
| } | ||
| ``` | ||
|
|
||
| **Limitations**: | ||
|
|
||
| - Hardcoded, scattered across codebase | ||
| - No feature flag support | ||
| - Difficult to test multiple versions | ||
| - Hard to track version changes | ||
|
|
||
| **Migration path**: | ||
|
|
||
| 1. Extract to config.ts | ||
| 2. Then migrate to Pattern 1 (versioning-info.ts) | ||
|
|
||
| ## Pattern 4: Version in URL Path vs Header | ||
|
|
||
| Some APIs use version differently: | ||
|
|
||
| ### URL Path Version | ||
|
|
||
| ```typescript | ||
| const BASE_URL = `https://api.example.com/${API_VERSION}` | ||
| // Results in: https://api.example.com/v4/users | ||
| ``` | ||
|
|
||
| ### Header Version (Klaviyo) | ||
|
|
||
| ```typescript | ||
| headers: { | ||
| 'revision': '2025-01-15' // ← Version as header | ||
| } | ||
| // URL stays: https://a.klaviyo.com/api/profiles/ | ||
| ``` | ||
|
|
||
| ### Query Parameter Version | ||
|
|
||
| ```typescript | ||
| const url = `${BASE_URL}/endpoint?api-version=${API_VERSION}` | ||
| // Results in: https://api.example.com/endpoint?api-version=v4 | ||
| ``` | ||
|
|
||
| ## Feature Flag Naming Convention | ||
|
|
||
| **Pattern**: `{destination-slug}-canary-api-version` | ||
|
|
||
| **Examples**: | ||
|
|
||
| - `cm360-canary-api-version` (Google Campaign Manager 360) | ||
| - `klaviyo-canary-api-version` (Klaviyo) | ||
| - `the-trade-desk-crm-canary-api-version` (The Trade Desk CRM) | ||
|
|
||
| **Usage in code**: | ||
|
|
||
| ```typescript | ||
| export const FLAGON_NAME = 'destination-canary-api-version' | ||
|
|
||
| // In actions | ||
| async perform(request, { payload, settings, features }) { | ||
| const version = getApiVersion(features) | ||
| // use version... | ||
| } | ||
| ``` | ||
|
|
||
| ## Testing Both Versions | ||
|
|
||
| **Standard pattern**: | ||
|
|
||
| ```typescript | ||
| import { FLAGON_NAME } from '../utils' | ||
|
|
||
| describe('SomeAction', () => { | ||
| it('should use stable version by default', async () => { | ||
| const responses = await testDestination.testAction('actionName', { | ||
| event, | ||
| mapping, | ||
| settings | ||
| // features undefined = stable version | ||
| }) | ||
|
|
||
| expect(mockRequest).toHaveBeenCalledWith(expect.stringContaining(API_VERSION)) | ||
| }) | ||
|
|
||
| it('should use canary version with feature flag', async () => { | ||
| const responses = await testDestination.testAction('actionName', { | ||
| event, | ||
| mapping, | ||
| settings, | ||
| features: { [FLAGON_NAME]: true } // ← Enable canary | ||
| }) | ||
|
|
||
| expect(mockRequest).toHaveBeenCalledWith(expect.stringContaining(CANARY_API_VERSION)) | ||
| }) | ||
| }) | ||
| ``` | ||
|
|
||
| ## Migration Checklist: Simple → Canary Pattern | ||
|
|
||
| When migrating from Pattern 2/3 to Pattern 1: | ||
|
|
||
| - [ ] Create `versioning-info.ts` with both versions | ||
| - [ ] Export stable version with current value | ||
| - [ ] Export canary version with new value | ||
| - [ ] Add JSDoc comments with changelog URL | ||
| - [ ] Create `getApiVersion(features)` helper | ||
| - [ ] Define `FLAGON_NAME` constant | ||
| - [ ] Update `config.ts` to import from versioning-info | ||
| - [ ] Update all API call sites to use `getApiVersion()` | ||
| - [ ] Add `features` parameter to helper functions | ||
| - [ ] Update `extendRequest` to pass features | ||
| - [ ] Update action `perform` functions to receive features | ||
| - [ ] Add tests for both versions | ||
| - [ ] Verify feature flag toggles correctly | ||
| - [ ] Document in PR | ||
|
|
||
| ## Real-World Examples | ||
|
|
||
| ### Google Campaign Manager 360 | ||
|
|
||
| ```typescript | ||
| // versioning-info.ts | ||
| export const GOOGLE_CM360_API_VERSION = 'v4' // stable | ||
| export const GOOGLE_CM360_CANARY_API_VERSION = 'v5' // canary | ||
|
|
||
| // utils.ts | ||
| export const FLAGON_NAME = 'cm360-canary-api-version' | ||
| export function getApiVersion(features?: Features): string { | ||
| return features && features[FLAGON_NAME] ? CANARY_API_VERSION : API_VERSION | ||
| } | ||
|
|
||
| // Usage | ||
| const version = getApiVersion(features) | ||
| const url = `https://dfareporting.googleapis.com/dfareporting/${version}/userprofiles/...` | ||
| ``` | ||
|
|
||
| ### The Trade Desk CRM | ||
|
|
||
| ```typescript | ||
| // versioning-info.ts | ||
| export const THE_TRADE_DESK_CRM_API_VERSION = 'v3' | ||
|
|
||
| // functions.ts | ||
| const BASE_URL = `https://api.thetradedesk.com/${THE_TRADE_DESK_CRM_API_VERSION}` | ||
| export const TTD_LEGACY_FLOW_FLAG_NAME = 'actions-the-trade-desk-crm-legacy-flow' | ||
| ``` | ||
|
|
||
| ### Klaviyo (Simple → Canary Migration) | ||
|
|
||
| ```typescript | ||
| // BEFORE (config.ts) | ||
| export const REVISION_DATE = '2025-01-15' | ||
|
|
||
| // AFTER (versioning-info.ts) | ||
| export const KLAVIYO_API_VERSION = '2025-01-15' | ||
| export const KLAVIYO_CANARY_API_VERSION = '2026-01-15' | ||
|
|
||
| // AFTER (config.ts) - maintain compatibility | ||
| import { KLAVIYO_API_VERSION } from './versioning-info' | ||
| export const REVISION_DATE = KLAVIYO_API_VERSION | ||
|
|
||
| // AFTER (functions.ts) - add feature flag support | ||
| export const FLAGON_NAME = 'klaviyo-canary-api-version' | ||
| export function getApiRevision(features?: Features): string { | ||
| return features && features[FLAGON_NAME] ? KLAVIYO_CANARY_API_VERSION : KLAVIYO_API_VERSION | ||
| } | ||
|
|
||
| export function buildHeaders(authKey: string, features?: Features) { | ||
| return { | ||
| Authorization: `Klaviyo-API-Key ${authKey}`, | ||
| Accept: 'application/json', | ||
| revision: getApiRevision(features), // ← Dynamic based on feature flag | ||
| 'Content-Type': 'application/json' | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| ## Key Takeaways | ||
|
|
||
| 1. **Always use Pattern 1** (canary with versioning-info.ts) for new destinations | ||
| 2. **Migrate older patterns** when doing version upgrades | ||
| 3. **Feature flags are mandatory** for API version changes | ||
| 4. **Test both versions** in your test suite | ||
| 5. **Document changelog URL** in version constant comments | ||
| 6. **Use consistent naming** for feature flags | ||
| 7. **Pass features through** the entire call chain | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.