Backend API updates
Audience, messages, events, and operations
This guide covers the current /v1 REST contracts for backend integrations. All app routes use https://app.openpush.ai/v1/apps/{app_id}. Send X-OP-API-Key: <rest-key> for administrative calls. An app's SDK key is limited to device and event ingestion. Keep REST keys on a trusted server.
The generated API reference has the route inventory and request schemas. Examples below use native OpenPush resource names and IDs.
Create and maintain users
POST /users creates an account profile and, optionally, its first push subscriptions in one transaction. A subscription requires an explicit consent boolean. An alias value or external_id already held by another user returns 409; a failed subscription leaves no new user behind.
Code
Use GET /users/{user_id}, GET /users/by-external-id/{external_id}, or GET /users/by-alias/{label}/{value} to read a profile. PATCH /users/{user_id} changes country, language, timezone, or email; pass null to clear one of those properties. Email storage requires the app's email collection switch. Tag and alias writes continue to use their dedicated profile routes.
DELETE /users/{user_id} erases the profile and associated identity data and retires its subscriptions so future sends cannot target them. The receipt names retained deidentified delivery history and audit records. Existing aggregate counts are not reconstructed.
Maintain push subscriptions
Use POST /users/{user_id}/subscriptions with the subscription body above to add a push device. PATCH /subscriptions/{sub_id} accepts consent, new_token, device metadata, and iOS sandbox. A consent value of false opts out without deleting delivery history. An invalid provider token needs a fresh device registration before opting back in.
PATCH /subscriptions/by-token accepts the current token in the JSON body along with update fields. Keep tokens out of URL paths and ordinary logs. POST /subscriptions/{sub_id}/transfer accepts {"user_id":"usr_..."} and preserves the subscription ID. The source and destination must belong to the same app. DELETE /subscriptions/{sub_id} retires the device; subsequent audience resolution excludes it.
Target an audience directly
POST /audience-preview and POST /messages share the same selectors. Generic inline filters narrow the audience without saving a segment:
Code
Predicates reuse the saved-segment field/operator vocabulary, including tag, profile, device, session, test-user, and location fields. They combine with AND by default; an {"operator":"or"} entry starts another OR group, while predicates inside a group remain ANDed. Tag rules require key; bounded in and not_in lists work across supported tag and profile fields. Missing tags match not_in and not_exists, but not in or exists. Filters intersect with included segments, excluded segments, and a target selector.
For a known set of recipients, use one identity selector under target: external_ids, subscription_ids, or aliases: {"label":"account","ids":["42","43"]}. Each list accepts 1 to 100 IDs and duplicate values are counted once. Single external_id, subscription_id, and alias selectors remain supported. platform or platforms may narrow the result. Unknown or misspelled audience fields return 400; they cannot turn a targeted request into an all-subscriber send. Preview reports matching subscriptions and invalid requested IDs separately.
For saved segments, GET /segments/{seg_id} returns one editable segment. Segment rules support bounded in and not_in lists for tags, language, country, and app version. Segment session metrics refer to users, rather than individual devices.
Message content and delivery
platform_options accepts the supported iOS and Android presentation fields and validates them before a message is created. For example:
Code
Platform display can depend on device settings and the installed client version. Provider acceptance is recorded separately from device receipts. The API rejects unsupported presentation fields.
POST /templates and PATCH /templates/{tpl_id} accept languages, default_language, and platform_options alongside the basic title, body, image, link, and custom data. Each localized entry requires a title and body. GET /templates/{tpl_id} returns the saved content and version. On send, explicit message content and platform fields override template defaults. A created message stores its effective content, so later template edits do not alter a scheduled message.
delivery_policy can tighten the app's frequency_cap or throttle_per_minute for one message. Values must be positive integers within the app's limits. It cannot bypass the app's safety settings; message throttling cannot be combined with best-hour delivery. Preview and dispatch apply the stored policy.
GET /messages supports a stable cursor and exact status and name filters. DELETE /messages/{message_id} cancels a message only while its status is Scheduled. If dispatch has begun, the call returns 409; it cannot recall a push already sent.
Retry custom events
POST /events accepts up to 50 events per batch with an SDK key or a writable REST key. Give an event an idempotency_key when a caller may retry. Reusing a key with identical content returns the stored event with duplicate: true and does not trigger a second journey entry. Reusing it with different content is rejected for that item. The response retains accepted as the count of accepted items and adds inserted and duplicates; dropped and dropped_reasons report rejected items. Rate accounting is shared by server instances.
Export message activity
POST /messages/{message_id}/activity-exports creates an immutable snapshot. Supply types from accepted, received, confirmed, clicked, and failed. GET /activity-exports/{export_id} returns counts, expiry, and a download path. The download is newline-delimited JSON with a schema version, message and subscription IDs, timestamps, and an evidence source. Provider acceptance does not mean the device displayed the message.
Snapshots expire after 24 hours. Each app may keep three active snapshots; a message may have at most 50,000 delivery rows and the stored file at most 20 MB. A missing cross-app export returns 404, and an expired export returns 410.
App metadata and keys
GET /v1/apps/{app_id} reads app metadata. PATCH /v1/apps/{app_id} changes name, icon_url, tile_color, or play_store_url.
POST /keys creates a REST or SDK key and returns its secret once. GET /keys lists metadata without secret values. PATCH /keys/{key_id} changes the name, REST scope (full or read), enabled state, or up to 16 CIDR allowed_ips ranges. DELETE /keys/{key_id} revokes a key. The last active full administrative key cannot be disabled or deleted.
POST /keys/{key_id}/rotate returns the new secret once and leaves the old REST secret valid for 24 hours or the old SDK secret valid for seven days. The response reports the old secret's expiry. IP restrictions are checked against the request peer address, or a forwarded address only when the server trusts that proxy network.
Compatibility transition
Existing send and device registration requests keep their native fields. The event batch's accepted count retains its previous meaning; inserted and duplicates are additional fields. Calls that used to recover secrets from GET /keys must read the secret at creation or rotation. Until 2026-10-24 UTC, GET /keys?include_values=true provides explicit legacy readback; after that date it returns 410.
Targeting is intentionally strict: previously ignored audience misspellings now return 400 to prevent an accidental broad send. Test request bodies against POST /audience-preview before scheduling campaigns.