/partner/v1/ integration to the
Partner API at /partner/v2/. Work through Part A to change your code, then use
Part B to move companies across one cohort at a time.
Use the frozen Partner introduction
only to inventory old behaviour; build the new flows from the current
Partner overview and
resource model.
Use the documented trailing slash on every /partner/v2/ root and route,
and on delegated /v2/ requests. Slashless requests return JSON 404 for
every method, including GET, HEAD, OPTIONS, and writes, with no redirect
or Location header. Update request builders to send canonical URLs directly;
see canonical URLs.
Outcome
Your integration onboards companies through/partner/v2/, manages people as
memberships below their company, reads readiness and delegated access as two
separate answers, and submits Case work with a company header instead of a user
header. No legal company exists twice, and no person-company intent is
processed twice during cutover. Companies already managed through v1 are the
same records in v2 and retain their company UUIDs; they need no import or
recreation.
What actually changes
The company record and its UUID are shared across versions. Its legal identity, address, tax treatment, VAT number, default claim type, payout bank account, notification channels, and legal representatives are the same concepts you already send — mostly renamed or moved to enum codes. Four things change:- A user is no longer a top-level resource. Global
/partner/v1/users/and/partner/v1/userinvites/collapse into one nested membership below a company:POST /partner/v2/companies/{company_id}/users/. There is no global user listing and no separate onboarded-user lookup. - The tenant selector changed.
X-User-Idis removed. Every delegated/v2/Case call selects the company withX-On-Behalf-Of-Company: <company-uuid>. - Onboarding readiness split into two questions. v1 treated
data_submission_completedas the single gate. v2 answerscase_submission_readiness(is the company’s data valid?) andcase_access(may the Partner act right now?) independently. - Writes are idempotent and errors are typed. Every logical
POSTcarries a stableIdempotency-Keywith exact replay semantics.
POST /partner/v1/userinvites/ returned a
redirect URL for the v1 web flow. The v2 membership command sends setup mail
and returns the fresh invitation once as setup_url; this is not a v1-style
hosted-onboarding redirect. Hosted onboarding is a separately arranged
paywise flow, not an invite-API replacement.
Prerequisites
- Partner credentials verified for v2 in the intended sandbox environment. Shared company identity does not establish credential compatibility or access.
- A migration ledger. For each legal company, retain its existing company UUID, the authoritative external business ID, environment, migration state, readiness and access snapshots, and a verification time. For each person-company relationship, retain the v1 user or invite ID separately from the verified v2 membership UUID, where a corresponding membership exists.
- An inventory of v1 companies, users, invites,
X-User-Idcall sites, credentials, hosted redirects, background jobs, and Case write routing, including every in-flight onboarding state.
Conventions that apply to every step
Pagination. Company and membership lists are paginated. Follow each exactnext URL. Mutable offset pagination has no snapshot: use inclusive
updated_since where declared, overlap scans, reconcile by (id, updated_at),
and schedule full scans. Never choose the first result by position, infer
uniqueness from a search term, or advance a high-water mark before the whole
sweep is durable.
Errors. v2 uses a typed envelope and X-Paywise-Request-Id. Branch on
stable codes and field paths. Treat 400 as input or context validation, 401
as a credential/host/environment mismatch, 403 as an access or
entitlement failure, 404 as a wrong or intentionally hidden company or
resource context, and 409 as a lifecycle, identity-lock, last-admin, access,
or idempotency conflict. After 429, wait at least the integer Retry-After
value.
Idempotency. Use a fresh stable Idempotency-Key for each logical v2
POST: company create, membership add/resend/cancel, webhook management, and
delegated Case commands. Store the key, canonical body hash, company UUID,
operation, and outcome before dispatch. Retry an ambiguous timeout or transient
5xx with the exact same path, body, context, and key. A replay has a fresh
request ID but the same stored business response; a changed body or operation
needs a new logical key.
Part A — change your code
1. Get v2 credentials and delete X-User-Id
Both contracts describe HTTP Bearer schemes. That proves only the header shape;
it does not prove that a v1 credential is accepted by v2 Partner or Case
endpoints. Obtain or confirm separately authorized v2 sandbox and production
Partner credentials through the authorized portal or staff process. Never copy
or test a production secret in sandbox. The public contracts expose no operation
to create, list, mint, rotate, or revoke Partner or per-company API keys, and
there is no public per-company Partner secret or token lifecycle endpoint.
Verify a candidate key with GET /partner/v2/info/ and a safe scoped company
read. Require the authenticated response’s case-insensitively parsed
X-Paywise-Environment header to equal the intended environment; a missing
header fails verification. Store only secret-manager references and credential
metadata.
Partner management calls use the Partner key directly and must not send a
company-selection header:
/v2/ request:
Migrate the info-response parser
FrozenGET /partner/v1/info/ returns a JSON array. Its schema sets no minimum
or maximum cardinality, so a parser must handle zero, one, or multiple entries
without selecting position zero. Each entry may expose the token owner’s email,
first name, and last name as user, user_first_name, and user_last_name:
migration-example=partner-v1-info-response
GET /partner/v2/info/ returns one non-PII object, not an array or a paginated
envelope. It removes the token-owner fields and adds the authenticated
environment and owning partner projection. The response also retains the
credential’s server-issued access metadata:
migration-example=partner-current-info-response
environment with the case-insensitively parsed
X-Paywise-Environment response header. Remove owner email and name columns
from the new token-info projection, and purge or restrict the legacy PII
according to retention policy. A missing or null token or partner identifier
must fail any verification step that requires identity, even though the schema
permits nullable projections.
Follow authentication and environments
and calling the Case Management API.
2. Reuse existing companies and port new company creation
An existing v1 company is immediately addressable through v2 with its existing UUID, subject to current access. Refresh its company details, readiness, access, and memberships through v2. Keep its onboarding progress and existing cases; do not call company create or send onboarding invitations merely to switch API versions. For a new company, port the creation flow below. v1 created a company, then attached people with separate top-level commands:migration-example=partner-v1-company-request
migration-example=partner-current-company-request
Legal representative types:
Companies onboarded through v1 read back in the English codes.
Persist the response’s company and membership UUIDs directly:
migration-example=partner-current-company-response
3. Replace top-level users and invites with nested memberships
New v2 invitations use a company-scoped membership. Before creating one for an existing company, fully paginateGET /partner/v2/companies/{company_id}/users/
and resolve the intended relationship from the returned membership records.
Store the verified membership UUID with its company UUID. Do not substitute an
old global user or invite UUID, and do not assume every old invitation has a
direct v2 membership equivalent. Reconcile pending onboarding before sending
a new invitation.
For new onboarding, this is what v1 sent:
migration-example=partner-v1-user-request
Retain the returned membership UUID; an old global user ID is not a
membership ID. Every add returns
pending_setup, whether the address is new
or already belongs to a paywise account; the public response deliberately
does not reveal which global user path occurred. Only pending memberships can
change role, resend, or cancel. The invite link is returned once as
setup_url on the add and resend responses and omitted from reads and
idempotent replays; it leads to password setup only for an account the add
created and to sign-in for every account that existed before. Follow
manage memberships.
Membership add is also resource-idempotent: the same company, case-insensitive
email, and role may return the existing membership UUID without sending another
email or event. That does not make email a safe resource ID, and it does not
justify racing two adds.
skip_email_verification: what it does and does not do
skip_email_verification: what it does and does not do
Both the frozen v1 and the v2 wire schemas call the field
skip_email_verification. One inherited frozen prose page calls it
skip_email_validation; that is a documentation typo, not a wire alias, so
never send it.The v2 flag requires a staff-controlled Partner entitlement and only asserts
out-of-band email ownership for a newly created global user. It does not
skip password setup, terms, named-Partner confirmation, or setup mail; it does
not skip the initial pending_setup membership state; and it reveals nothing
about an already existing global user.4. Read readiness and access as two separate answers
v1 treateddata_submission_completed as the onboarding gate. v2 splits it,
and you must check both.
case_submission_readiness.ready answers whether the company’s data can pass
Case finalization. Its issue objects contain actionable field, code, and
message values — render remediation from those pairs rather than
reconstructing your own. Draft creation can occur before readiness is complete;
finalize revalidates it atomically.
case_access answers whether the Partner may currently act, and exposes only
available or unavailable. Provisional API-onboarding access can be
available before confirmed consent; conversely, complete company data can be
ready while delegation is unavailable.
Require both projections separately before every finalize. Follow
readiness and access and
resolve readiness.
5. Provision Partner-owned webhooks
Creating a company does not create a webhook subscription. Choose the owner and event coverage explicitly:/partner/v2/webhooks/subscriptions belong to the Partner and receive company, membership, readiness, and access events. Add Case events with acompaniesselector ("*"or a list of company UUIDs). Manage them with the Partner key and no company header./v2/webhooks/subscriptions belong to one company and receive Case events. A direct Case credential manages them. A Partner key withX-On-Behalf-Of-Companymay read them but cannot write them (403 use_partner_webhooks).
2xx, then refetch authoritative state. Readiness events are
signals, not complete issue reports.
Create and bodyless rotate expose secret_key once through their documented
one-time-secret response schema. Test and manual-redelivery commands return
202 Accepted with required detail, delivery_id, and event_id;
redelivery creates a new delivery ID and preserves the event ID. Subscriptions
support events: ["*"], while test-event selection remains limited to concrete
events. Never substitute X-Paywise-Request-Id for either identifier.
Use the access-change workflow,
consume Partner webhooks,
and webhook ownership.
6. Route delegated Case calls by company
Every Case command your platform issues on a company’s behalf now carriesX-On-Behalf-Of-Company and no user header. Route each business command by the
exact company UUID and command ID to exactly one contract — v1 or v2, never
both. When case_access is unavailable, gate and quarantine unsent work; do
not fall back to X-User-Id or another credential.
The Case-side migration itself — orders, finalize, documents, payments,
mandates — is covered by the
Case Management migration guide.
See also submit cases for a company.
Part B — cut over
Coexistence without duplicate companies or commands
Select one dispatch path for each logical command in the migration ledger. Both versions operate on the same company and Case records:
Company read comparison can run in parallel, but raw payload equality is not a
goal. Compare legal identity, addresses, tax treatment, notification coverage,
membership intent, and business readiness while respecting the new models. Keep
the existing company UUID for current paths and audit. Keep old user and invite
IDs separately from company-scoped membership UUIDs.
Staged rollout
- Inventory v1 companies, users, invites,
X-User-Idcall sites, credentials, hosted redirects, background jobs, and Case write routing. Record every in-flight onboarding state. - Obtain or confirm v2 sandbox credentials and verify them. Read each existing company by its stored UUID and resolve its membership UUIDs without blind creates.
- Transform golden company and user fixtures and exercise API-only onboarding, membership recovery, readiness remediation, and delegated draft/finalize in sandbox with authoritative environment proof.
- Create Partner-owned sandbox subscriptions with the required company coverage. Drill duplicates, out-of-order access events, and secret rotation.
- Canary one explicit company cohort. Stop its v1 mutations, switch new onboarding and Case commands atomically, and keep other companies on v1.
- Expand only after the verification gates pass. Then stop new v1 Partner writes, verify historical records through v2, and retire v1 credentials and subscriptions through the authorized process.
Verification gates
- Every existing legal company retains its UUID and no duplicate is created by the migration; every resolved membership has one company parent and its own verified UUID.
- Company detail matches the intended legal, tax, bank, and notification data and has a non-cancelled admin; readiness issues are rendered from their field/code pairs rather than reconstructed.
case_accessand readiness are evaluated independently before every canary finalize; no v2 request sendsX-User-Id.- Partner management calls never send
X-On-Behalf-Of-Company; every delegated Case call sends the exact canary company UUID. - Exact same-key retry drills return the same company, membership, order, or delivery resource IDs while request IDs remain support-only.
- Paginated interruption and overlap drills converge and never claim a snapshot.
- Partner and Case webhook stores keep owners and secrets separate. Duplicate and reordered access events always end in a Company refetch, and a missed-restoration drill is repaired by scheduled reconciliation.
Roll back by stopping writes
Rollback is a write stop, not a reverse migration. Disable v2 Partner and delegated Case dispatch for the affected cohort, preserve all keys, bodies, company context, and response IDs, then reconcile every unknown outcome. Do not recreate a company, mint or seek an undocumented per-company key, replay commands withX-User-Id, or automatically route a timed-out v2 command to v1.
Previously committed changes remain on the shared company and Case records;
membership relationships also remain intact. Rollback changes only future
routing. Resume a v1 operation only after
an operator proves that the same business command did not commit in v2 and that
v1 is still its assigned authorized owner. For access loss, keep writes gated
until exact Company state is available; never use rollback to bypass delegation.