Skip to main content
This page tracks user-facing changes to the Privy API. Dates reflect the day a change went live in production. For migration help, email support@privy.com.

2026-11-09

List and look up contact tags

GET /v1/tags lists your account’s tags, and GET /v1/tags/{id} returns one by its id. Pass tag_type=shopify to list the tags synced from your Shopify customers instead of the tags applied in Privy, and q to match the start of a name. Both endpoints need contacts_read. The tag_… and stag_… ids in a flow’s conditions are tag ids, so you can look them up here to see the tag’s name.

Filter contacts by tag

GET /v1/contacts takes tag and shopify_tag filters. Pass up to 50 names, comma-separated or in array form (tag[]=vip&tag[]=wholesale). A contact matches if it has any of the names in a filter, and must match both filters when you pass both. Names match regardless of case or surrounding whitespace. Contacts now include shopify_tags alongside tags, including the contacts listed by GET /v1/segments/{id}/contacts.

2026-10-22

Read endpoints for campaigns, flows, displays, segments, coupons, reports, orders, products, imports, and your account

The API now returns what your account has set up, not just contacts. Every new endpoint is a GET, and each needs the read scope for its area.

New read scopes

Eight read scopes can now be selected in Settings → API Tokens when you create an API token or OAuth application: campaigns_read, coupons_read, displays_read, flows_read, orders_read, products_read, reports_read, and segments_read. Existing tokens keep the scopes they were created with. See Scopes for what each one allows. Report endpoints require reports_read on its own. A token with campaigns_read, flows_read, displays_read, or products_read can read those records but gets insufficient_scope on /v1/reports/*.

New error codes

Reads that depend on your connected store can return no_connected_store (404), store_rate_limited (429), and store_unavailable (502). Checking a code on a manual coupon pool returns unsupported_coupon_type (422). Contact reads on an account whose contacts predate its API access return contacts_not_ready (409). See Errors for details.

2026-06-30

Target subaccounts with a parent-account API token

Parent-account API tokens can now act on subaccounts. In Settings → API Tokens, choose whether a new token reaches Parent only (default), All subaccounts, or Selected subaccounts. Tokens created before this change keep Parent only behavior. To find reachable subaccounts, call GET /v1/account without the Privy-Account-Id header and use an id from the response’s data.subaccounts list. Add that identifier to the Privy-Account-Id header to target the subaccount. Omit the header to act on the parent, exactly as before. A token that reaches a subaccount gets its own rate-limit bucket for that subaccount. Reach is fixed at token creation — revoke and recreate the token to change it. See Subaccount access for details.

2026-07-03

Added POST /v1/events endpoint

You can send custom events to Privy through the API with the POST /v1/events endpoint. Accepted events can trigger Flows and other automations, keyed on the event_type you send. A successful 200 response means the event was accepted for processing — it does not guarantee a specific automation ran.

2026-06-22

Added POST /v1/orders/placed endpoint

You can create and update orders through the API with the POST /v1/orders/placed endpoint, available under the orders_write OAuth scope. The endpoint is an idempotent upsert keyed on order_id: the first call for a given order_id creates the order and returns 201 Created, while subsequent calls with the same order_id update the existing order and return 200 OK. Send the full current state of the order on every call — omitted optional fields keep their previously stored values.
  • Required fields: order_id, total, currency, order_date, plus at least one of email or phone.
  • Customer linkage. When an email or phone is provided, Privy finds or creates the matching contact. accepts_email_marketing drives email subscribe-on-create semantics, and accepts_sms_marketing records an SMS opt-in on newly created contacts (requires phone).
  • Historical imports. Pass initial_sync: true to load past orders without triggering real-time side effects such as flows, automations, and campaign-revenue attribution. Customer order stats are still updated so segmentation and winback stay accurate.

Tie orders to a Custom Integration with X-Privy-Integration-Token

POST /v1/orders/placed accepts an optional X-Privy-Integration-Token header that attributes the order to a specific Custom Integration. Generate the token when you create a Custom Integration in Settings → Integrations Hub, and retrieve it later with the Reveal action.
  • When the header is present, it must match an active Custom Integration for your business.
  • When the header is omitted and exactly one Custom Integration exists, the order is automatically tied to it.
  • Otherwise, the order is created with no integration attribution.
The header is sent in addition to your standard Authorization: Bearer OAuth token, which still authenticates the request.

2026-05-22

Welcome SMS for API-created SMS subscribers

When you set sms_consent to subscribed via POST /v1/contacts or PATCH /v1/contacts/{id}, Privy now automatically sends a TCPA-required welcome SMS to the contact. This only fires when the contact actually transitions to a confirmed opt-in state — contacts that are already subscribed are not messaged again. To suppress the welcome SMS (for example, when you already collected consent outside of Privy), pass send_welcome_sms: false in the request body. The parameter defaults to true.

2026-05-14

Contact creation conflict responses now include id

POST /v1/contacts now returns the id of the existing contact in 409 Conflict responses, nested inside the error object (error.id). This lets API consumers identify and update the existing contact without a separate lookup.

2026-05-12

Breaking changes:
  • Response field renames. Contact responses now return email_consent and sms_consent instead of email_permission and phone_permission.
  • Filter parameter renames. The GET /v1/contacts query parameters email_permission and phone_permission have been renamed to email_consent and sms_consent.
  • New consent values. The old non_subscribed value has been replaced by never_subscribed. New values have been added — see below.
New features:
  • Consent management via PATCH. PATCH /v1/contacts/{id} now accepts email_consent and sms_consent fields. You can combine consent changes with other field updates in a single request.
  • Consent on create. POST /v1/contacts now accepts the full set of writable consent values for email_consent and sms_consent.
  • Expanded email consent values:
    • subscribed — contact opted into email.
    • unsubscribed — contact opted out of email.
    • never_subscribed — no opt-in, no opt-out (replaces non_subscribed).
    • suppressed — merchant-suppressed (writable). Writing any consent value to a merchant-suppressed contact unsuppresses it first.
    • compliance_suppressed — system-suppressed (read-only, filterable). Any write to a compliance-suppressed contact returns 422.
  • Expanded SMS consent values:
    • subscribed — contact has confirmed SMS opt-in.
    • unsubscribed — contact opted out of SMS.
    • never_subscribed — no opt-in, no opt-out (replaces non_subscribed).
    • single_opt_in — merchant collected a single opt-in (not yet confirmed). Writable; requires a phone number.
    • pending — awaiting confirmation reply (read-only, filterable).
Deprecations:
  • POST /v1/contacts/{id}/unsubscribe is deprecated. Use PATCH /v1/contacts/{id} with email_consent: "unsubscribed" instead. The endpoint continues to work but now returns Deprecation: true and Link headers.