/api/ with no version segment, while a specific subset uses /api/v2/. There is no /api/v1/ prefix; the unversioned /api/ paths are effectively v1, and /api/v2/ represents newer or improved implementations on a per-endpoint-family basis.
This page covers what’s known about the model today, which endpoints use /v2/, what backward-compatibility commitments to assume, and how to write integration code that survives the platform’s evolution.
The current versioning state
What’s on /v2/ today
Two endpoint families currently use the /v2/ prefix:
| Endpoint family | Endpoints |
|---|---|
| Gift Transaction submission | POST /api/v2/Gift/Transaction (single), POST /api/v2/Gift/Transactions (batch) |
| Pledge management | POST /api/v2/Pledge (create), GET /api/v2/Pledge/{pledgeId}, GET /api/v2/Pledge/ByContact/{contactId}, POST /api/v2/Pledge/Query, GET /api/v2/Pledge/QueryOptions, GET /api/v2/Pledge/CustomFields, PUT /api/v2/Pledge/WriteOff/{pledgeId} |
/api/ with no version segment. The Contact endpoints, the (non-batch, non-v2) Gift endpoints, the Project endpoints, the RecurringGift endpoints, the Webhook endpoints, the Query endpoints for most resources — all unversioned.
Why /v2/ exists
The /v2/ prefix indicates a more recent, sometimes redesigned implementation of an endpoint family. For Gift Transactions, the /v2/ version is the recommended path for partner integrations submitting gifts. For Pledges, the /v2/ family is the only version exposed in the current spec.
This means partner integrations should prefer the /v2/ variant when it exists for the operation they’re performing. There’s no documented reason to call /api/Gift/Transaction (without /v2/) for a new integration. If a non-v2 variant exists for the same operation, treat the /v2/ one as canonical.
Writing code that anticipates /v2/ evolution
Partner integrations have a long lifespan — measured in years. The current /v2/ inventory is unlikely to be the final inventory; new families may be promoted to /v2/ over time, and at some point a /v3/ may appear. Three patterns help:
Pattern 1: configurable base path per endpoint family
Don’t hardcode/api/ or /api/v2/ throughout your codebase. Centralize the path construction:
JavaScript
Pattern 2: feature detection over hardcoded behavior
If two variants of an endpoint exist and the field shapes differ, detect what the platform accepts rather than assuming:JavaScript
Pattern 3: defensive deserialization
Don’t assume response payloads have exactly the fields documented in the spec — they may have extras (added in a non-breaking spec update) or omissions (if a field was deprecated). Parse defensively:JavaScript
Pick<> types if generating from the spec.
Spec-vs-live field typing
A backward-compatibility concern specific to CRM+: the OpenAPI spec types many fields asstring that the live API actually accepts (and sometimes returns) as native types — booleans, integers, dates.
| Spec says | Live API accepts | Live API returns |
|---|---|---|
string for booleans like isPrivate | Native true/false (and string "true"/"false") | Native true/false |
string for integers like anniversaryYear | Native 2010 (and string "2010") | Native 2010 |
string for dates like birthDate | ISO 8601 date string "1972-02-19" | ISO 8601 date string |
string for amounts | Native 500.00 or string "500.00" | Sometimes native, sometimes string |
- Don’t trust auto-generated SDKs that strictly enforce the spec types. They’ll send
"true"(string) for booleans and rejecttrue(native) in responses, both of which break against the live behavior. - Send native types in requests. They work today and align with what most modern APIs do.
- Parse defensively in responses. If you expect a boolean and get a string, coerce; if you expect an integer and get a string number, parse it.
JavaScript
The spec-vs-live typing mismatch is a known issue documented across the audit findings. The platform may eventually update the spec to match live behavior (or vice versa). Until then, write defensive parsing.⚠️ Human input required: Establish whether the spec or the live API is the canonical source for field types. Either bring the spec in line with live behavior, or document that the spec is the authoritative source and the live API will be aligned to match.
Backward compatibility commitments
Without an explicit versioning policy from the platform team, partner integrations should make conservative assumptions about what may and may not change.What’s safe to assume stable
The following kinds of changes would be backward-breaking and are unlikely to happen silently:| Stable | Why |
|---|---|
| Existing endpoint paths | Changing a path breaks every integration that uses it. |
| Existing field names in request bodies | Renaming a field rejects every request that uses the old name. |
| Existing field names in response bodies | Renaming a field breaks every consumer that reads the old name. |
The general request/response envelope structure (list/total for paginated responses, error.details[] for validation errors) | Same reason — wholesale envelope changes break everything. |
| Authentication mechanism (Bearer tokens, header name) | A breaking auth change would be a deliberate, announced migration. |
What may change without notice
The following kinds of changes are not breaking and partner integrations should expect them to happen:| May change | Implication |
|---|---|
| New fields added to response bodies | Don’t error on unexpected fields. |
| New optional fields added to request bodies | Continue sending the fields you’ve always sent. |
| New event types added to webhook payloads | Switch on known event types; ignore unknown. |
| New enum values added to existing fields | Don’t validate enums client-side against a hardcoded list. |
| Error message text changes | Switch on status codes and error codes, not message strings. |
| New endpoints added | Doesn’t affect your existing code. |
| Performance characteristics | Faster or slower; doesn’t affect correctness. |
What’s ambiguous
A few changes sit in the middle and require explicit confirmation when they happen:| Ambiguous | Resolution |
|---|---|
| New required field on an existing request body | Breaks existing callers — but might be argued as “the field was always required, just not documented.” Treat as breaking and require an announced migration. |
Status code changes on existing responses (e.g., 200 → 201 on creation) | The audit found a 200-where-spec-implies-201 case. Code defensively to handle either. |
| Field type changes (string → number on the wire) | Currently happens routinely due to the spec-vs-live mismatch. Parse defensively. |
Handling deprecated endpoints
If an endpoint is deprecated in a future version, the platform will typically:- Add a new endpoint that supersedes it (often under a new version prefix like
/v2/or/v3/). - Continue to support the old endpoint for a deprecation window — typically months to years.
- Eventually remove the old endpoint.
Detect deprecation signals
The platform may emit deprecation warnings via response headers or via documentation. Specifically, watch for:| Signal | What to do |
|---|---|
Sunset HTTP header on responses | The endpoint will be removed on the date in the header. Plan migration before then. |
Deprecation: true HTTP header | The endpoint is deprecated but still functional. Migration recommended. |
A spec update marking an endpoint as deprecated: true | Same — plan migration. |
| A platform announcement (changelog, support email) | Same — plan migration. |
JavaScript
Migrate proactively
When a/v2/ (or future-version) variant is introduced for an endpoint family you use, plan migration on a reasonable timeline rather than waiting for sunset:
Read the new endpoint's documentation
Confirm what changed — field shapes, semantics, error conditions.
Implement against the new variant in a feature-flagged path
Both old and new code paths are present; the flag chooses between them.
Test against the Seeded Sandbox
Verify behavior parity between old and new before any customer is migrated.
Migrate customers gradually
Per-customer flags let you migrate one customer at a time and roll back if issues surface.
Defensive coding checklist
A set of practices that make integration code more resilient to platform evolution:- Endpoint URLs are constants in one place, not hardcoded throughout the codebase.
- Field access is by-name lookup with sensible defaults, not strict shape destructuring.
- Response parsers handle both native and string forms of typed fields (booleans, integers, amounts, dates).
- Webhook event handlers switch on known event types and ignore unknown ones, rather than failing.
- Enum values are not hardcoded for validation — let the API reject invalid values instead of pre-validating against a stale enum list.
- Status code handling is by status class (
2xx,4xx,5xx) for retry logic, with explicit branches for known specific codes (401,404,409,422,429). - Deprecation headers and
Sunsetheaders are logged or metric-counted, not silently ignored. - SDK code generated from the spec is reviewed before adoption — strict implementations break against the spec-vs-live typing differences.
Where to go next
Error Recovery Patterns
The error-handling practices that complement the defensive coding patterns on this page.
Base URLs and Environments
The reference for the host-level URL structure and how
/v2/ paths fit into it.Sync Architecture Patterns
The architectural patterns that the migration approach on this page builds on.
Changelog
The platform’s evolution log — watch for deprecation announcements and new versioned endpoints.