OneSignal Documentation Audit
The docs are broad and well-instrumented for agents (llms.txt, llms-full.txt, OpenAPI, MCP server), but the surface area has accumulated contradictions and silent gotchas: retention windows disagree across three pages, a "no deadline" migration guide sits next to a changelog with hard deprecation dates, the legacy Player Model still lurks in machine-readable indexes, the 21st subscription quietly orphans the oldest one, and channel selection works three different ways depending on which endpoint you read.
1. Audit log retention contradicts itself across three pages (critical)
Location: /docs/en/audit-logs.md, /llms-full.txt, /release-notes/changelog.md (March 20, 2026)
Problem: Three different statements about audit log retention exist in the scraped docs:
audit-logs.md: Free 48h, Growth 48h, Professional 48h (90 days with add-on), Enterprise 90 days.llms-full.txt: "48 hours for Free/Growth, 90 days for Enterprise" (omits Professional entirely).- Changelog (March 20, 2026): "Audit Log Export entered GA (2 days free tier; up to 90 days with security entitlement)" — "2 days" is not "48 hours" exactly, and "security entitlement" is a third name for what
audit-logs.mdcalls an "add-on" and what the rest of the docs call "the audit logs entitlement."
Consequence: Compliance and security teams reading any one page will pick the wrong number. Agents indexing llms-full.txt will tell users Professional gets 48h (correct) or 90 days (if they infer from "paid plans"). The naming inconsistency (add-on vs entitlement vs security entitlement) makes it impossible to know what SKU to actually buy.
The fix: Publish a single canonical retention matrix and link to it from llms-full.txt, the changelog entry, and the audit-logs page. Pick one term — "audit logs entitlement" — and use it everywhere.
2. Legacy Player Model endpoints still listed in llms.txt despite "migration is one-way" (significant)
Location: /llms.txt, /docs/en/user-model-migration-guide.md
Problem: user-model-migration-guide.md states: "The User Model is not backwards compatible. Migration is one-way." But llms.txt continues to enumerate legacy/player-model endpoints (add-a-device, edit-device, view-device, view-devices, edit-tags-with-external-user-id) alongside the new user-model endpoints — with no marker indicating which set is deprecated.
Consequence: Coding agents indexing llms.txt will treat both surfaces as equally valid and may generate code against the legacy player endpoints, which the migration guide warns are deprecated. Worse, the changelog (March 17, 2026) announces deprecation of amount_spent, rooted, bought_sku, app_interests, purchased_items effective April 15, 2026 — all player-model artifacts — so agents writing today against legacy endpoints will produce code that breaks within weeks.
The fix: Either remove legacy endpoints from llms.txt, or annotate each line with a (deprecated) marker plus a target sunset date. Mirror this in the OpenAPI spec.
3. User Model migration has no deadline, but its legacy fields do (significant)
Location: /docs/en/user-model-migration-guide.md, /release-notes/changelog.md (March 17, 2026)
Problem: The migration guide states: "No specific deadline is mentioned in the documentation." But the changelog announces hard deprecation of legacy player-model fields (amount_spent, rooted, bought_sku, app_interests, purchased_items) effective April 15, 2026, with described "Impact: Segments paused, Journeys archived, fields return empty in API/webhooks."
Consequence: Customers reading the migration guide assume they have time. Customers not reading the changelog discover on April 15 that their Segments are paused and Journeys archived. The two documents disagree about how urgent migration is.
The fix: Add a "Sunset timeline" section to the migration guide that incorporates the changelog's April 15, 2026 deprecation dates. Cross-link the changelog entry from the migration guide.
4. Server SDK version table mixes pinned versions with "Latest" — agents can't pin (significant)
Location: /docs/en/server-sdk-reference.md
Problem: The table lists:
- Node.js: "Latest on npm"
- Python: "Latest on PyPI"
- Java:
5.3.0 - Go:
v5 - PHP:
^5.3 - Ruby:
~5.3 - C# (.NET): "Latest on NuGet"
- Rust:
5.3.0
The doc claims "All OneSignal server SDKs are generated from the same OpenAPI specification, so they share a consistent interface" — but if Node/Python/C# float on "latest" and others are pinned at 5.3.0, they are demonstrably not the same version.
Consequence: Agents and humans writing package.json/requirements.txt/go.mod files can't know what version to pin. A bug filed against "the OneSignal Node SDK" can't be reproduced without further investigation. The "consistent interface" claim is undermined.
The fix: List pinned current versions for every language, with a "latest as of <date>" footnote. Regenerate the table on each release.
5. Identity Verification has no timeline for wrapper SDKs (significant)
Location: /docs/en/identity-verification.md
Problem: "SDK Support: Android SDK 5.2.0+ and iOS SDK 5.3.0+. Wrapper SDKs (Flutter, React Native, Unity) support is forthcoming." No date, no tracking issue link, no workaround. The flutter-sdk-setup.md page requires onesignal_flutter: ^5.1.2 and gives no warning that Identity Verification will not work.
Consequence: A team building a Flutter app that needs Identity Verification (a security feature, not a nice-to-have) has no way to know whether to wait, escalate, or build around it. Anyone making an architecture decision based on this doc is blind.
The fix: Either commit to a quarter for wrapper SDK IV support, or document a server-side mediation pattern teams can use until parity ships. Cross-link from each wrapper SDK setup page.
6. Confirmed receipt limitations are scattered and easy to miss (significant)
Location: /docs/en/confirmed-delivery.md
Problem: Confirmed receipt has at least five hard limitations buried in the table and "Key Limitations" list:
- "Available only on paid plans"
- "Only works if the device has the OneSignal SDK installed"
- "Not supported for subscriptions created via API only"
- "Safari does not support confirmed receipt"
- iOS: "APNs keeps only one message per app when offline"
- Huawei: "Supported only for the data message type"; receipt data only available in Huawei dashboard
Consequence: "Subscriptions created via API only" is a particularly insidious gotcha — teams that import subscribers via CSV/REST without the SDK on-device will see no confirmed receipts, and the docs don't surface this in the setup flow or in subscriptions.md. A team building delivery SLAs around confirmed receipt may find their data silently incomplete.
The fix: Add a "Will this work for my setup?" decision matrix at the top of the confirmed-delivery page covering subscription origin (SDK vs API), platform, and plan tier. Cross-link from subscriptions.md and the REST API user creation reference.
7. Throttling silently overrides Timezone & Intelligent Delivery (significant)
Location: /docs/en/throttling.md
Problem: "Throttling takes precedence over Timezone and Intelligent Delivery features. These scheduling options are ignored when throttling is enabled. To use either feature, disable throttling for that specific message." Additionally: "Throttling applies only to push notifications sent via the API or dashboard Messages interface—not to Journeys." And: per-minute setting is divided by 60 and rounded down (1,019/min becomes 16/sec → 960/min actual).
Consequence: A marketer schedules a campaign with timezone delivery and intelligent timing enabled, then sets a throttle rate, and quietly loses both scheduling features without an error or warning. Journey-sent pushes ignore throttling entirely. The integer-division rounding means configured rates silently under-deliver by up to 59/min.
The fix: Surface a warning in the dashboard composer when throttle is enabled alongside Timezone/Intelligent Delivery. Document the rounding behavior next to the API field (throttle_rate_per_minute). Explicitly call out that Journeys ignore throttling at the Journey configuration step.
8. Two authentication schemes (Key vs Bearer JWT) with no central reference (significant)
Location: /reference/rest-api-overview, /docs/en/identity-verification.md
Problem: When Identity Verification is enabled, "all user-related API endpoints require JWT bearer tokens: Authorization: Bearer <JWT>." But the REST API overview and the documented push endpoint show Authorization: Key ... as the standard auth format. There is no list of which endpoints flip to Bearer when IV is on, and no example showing the headers side-by-side.
Consequence: A team enabling Identity Verification mid-project gets cryptic 401s on user-management endpoints and has to discover the auth scheme change by reading the IV page. Agents generating client code will get the auth header wrong half the time.
The fix: Add an "Authentication" section to the REST API overview that enumerates both schemes and lists exactly which endpoints require Bearer when IV is enabled. Reflect this in the OpenAPI security schemes.
9. The 21st subscription silently orphans the oldest one (significant)
Location: /docs/en/subscriptions.md
Problem: "When a 21st subscription is added, OneSignal removes the External ID from the oldest Subscription (based on last session) creating an anonymous user. However, at least 3 Email and 3 SMS subscriptions are always retained." This is a destructive, silent operation: the External ID — the only bridge between OneSignal's profile and the customer's user database — is stripped from a real subscription, producing an anonymous orphan record. No webhook, no error, no warning is documented.
Consequence: A user with a heavy device history (browser-clearing power user, multi-device household, kiosk shared identity) silently fragments. Their oldest device becomes an unaddressable anonymous subscriber while still receiving messages. Reconciliation pipelines that match by External ID will drift over time, and there's no way to detect this is happening without inspecting subscription audit history. This is at least as severe as the confirmed-receipt limitations called out elsewhere.
The fix: Emit an event/webhook when the 21st-subscription orphaning fires, and document the behavior at the top of the subscriptions page with a worked example. Better: surface a subscriptions_pruned counter on the user profile so customers can detect and remediate.
10. Channel selection works three different ways across push/email/SMS (significant)
Location: /reference/push-notification.md, /reference/email.md, /reference/sms.md, /docs/en/web-sdk-setup.md
Problem: OneSignal documents three coexisting mechanisms to specify the messaging channel on /notifications:
- Push reference:
POST https://api.onesignal.com/notifications?c=push— query parameter. - Email reference:
POST /notifications?c=emailplustarget_channel required with include_aliases— query + body field. - SMS reference:
POST /notifications?c=smsplustarget_channel: "sms"required when using segments or aliases. - Web SDK setup curl example: hits
/notificationswith body fieldtarget_channel: "push"and no?c=query parameter.
When is target_channel required? When is ?c= required? When are both? The docs never centralize this.
Consequence: Agents and developers will guess wrong. A copy-paste from the web SDK curl works without ?c=; a copy-paste from the email reference requires both. A library author building a OneSignal wrapper has to pick a convention and pray it stays valid across endpoints.
The fix: Pick one mechanism (either query param or body field), deprecate the other, and document the migration path. Until then, add a "Channel selection" section to the REST API overview that says explicitly, per endpoint, which form is required.
11. Email troubleshooting page tells customers to publish a Mailgun dependency in DNS (minor)
Location: /docs/en/email-troubleshooting.md
Problem: The SPF simplification advice reads: "This works because OneSignal uses Mailgun as its underlying ESP, so both includes reference the same IP ranges." The recommended SPF record is then v=spf1 include:mailgun.org ~all — instructing customers to publish their dependency on Mailgun in DNS records on their own domains.
Consequence: Customers who care about vendor independence learn about the Mailgun dependency from a troubleshooting page, not from billing/architecture docs. More practically: telling customers to use include:mailgun.org directly couples their SPF to Mailgun's IP space, so if OneSignal ever changes ESPs, every customer with this simplified record breaks silently. The include:spf.onesignal.email indirection exists precisely to prevent that.
The fix: Remove or warn against the recommendation to bypass spf.onesignal.email. Investigate whether the underlying problem can be fixed without telling customers to hard-code an ESP-specific include — at minimum, warn customers that bypassing the OneSignal include will break if/when the ESP changes.
12. View Messages API excludes Journey-sent messages with no alternative pointed to (minor)
Location: /reference/view-messages.md
Problem: "Does not return Journey-sent messages." Data retention also differs sharply: API-sent retained 30 days, Dashboard-sent retained "for app's lifetime."
Consequence: A reporting integration that queries /notifications to reconcile sends will silently miss everything sent by Journeys — likely the majority of messages for any mature customer. The doc doesn't say where to get Journey send history (Event Streams? Audit logs? Analytics export?).
The fix: Add a "To get Journey-sent message history, use [Event Streams / CSV export / etc.]" pointer at the top of the View Messages reference. Document the 30-day API-message retention as a hard limit, not a footnote.
13. Payload-size limits scattered across endpoints with no central reference (minor)
Location: /reference/rate-limits.md, /reference/create-custom-events.md, /docs/en/custom-events.md, /reference/email.md, /reference/sms.md
Problem: Related payload limits live on five different pages with no consolidation:
- Custom Events:
2,024 bytesper event (rate-limits.md), written as2024 bytesoncreate-custom-events.mdandcustom-events.md. - Email
custom_data: 10 KB max. - SMS
custom_data: 2 KB max. - Custom Events request body: 1 MB max for the full batch.
The 2,024 vs 2024 formatting inconsistency is the surface symptom; the deeper issue is that a developer building a payload-validation helper has to spelunk five pages to gather all four limits.
Consequence: A developer writing a payload validator at exactly 2 KB will hit silent rejections on Custom Events (24 bytes over) while passing SMS validation. Cross-endpoint helpers can't easily share a config. The 2,024 vs 2024 difference also reads at first glance as a typo for "2,048."
The fix: Publish a "Payload limits" reference table covering Custom Events, Email custom_data, SMS custom_data, and request-body totals. Standardize numeric formatting (2,024 bytes everywhere).
14. Authorization header casing differs between reference pages (minor)
Location: /docs/en/keys-and-ids.md, /reference/push-notification.md, /docs/en/web-sdk-setup.md
Problem: The same auth scheme is documented with three different casings:
keys-and-ids.md:Authorization: key YOUR_REST_API_KEY(lowercasekey)push-notification.md:Authorization: Key YOUR_APP_API_KEY(capitalKey)web-sdk-setup.mdcurl example:--header 'authorization: Key YOUR_APP_API_KEY'(lowercase header name, capital scheme)
Consequence: HTTP header names and scheme tokens are case-insensitive per RFC 7235 § 2.1, so this is not a parsing-breakage issue. The cost is trust: when reference pages disagree on something this small, developers second-guess every other detail, and agents indexing the docs produce slightly different curl commands depending on which page they happened to scrape.
The fix: Standardize on Authorization: Key <token> across every reference page, code sample, and SDK doc. Add a single auth section to the REST API overview and link there from every endpoint.
What they do well
- Strong agent surface area:
llms.txt,llms-full.txt, public OpenAPI spec, and a beta MCP server with 31 tools — far ahead of most competitors on agent readiness. - Idempotency is well-specified: RFC 9562 UUIDs, 30-day validity,
Idempotent-Replayed: trueheader, explicit retry guidance with both standard and exponential-backoff strategies. - Server SDK breadth: 8 languages (Node, Python, Java, Go, PHP, Ruby, C#, Rust), all generated from one OpenAPI spec.
Top 3 recommendations
- Reconcile the silent gotchas — the 21st-subscription orphaning, the throttle-overrides-scheduling behavior, and the View-Messages-excludes-Journeys gap all need to be surfaced where customers will see them (at the top of relevant pages, plus webhooks/events where possible).
- Publish a User Model migration timeline — the migration guide says "no deadline," but the March 17 changelog scheduled hard deprecations for April 15. Reconcile the two and put dates on the migration page. While you're there, mark deprecated player-model endpoints in
llms.txt. - Add a global "API conventions" reference page — auth schemes (
KeyvsBearer), channel selection (?c=vstarget_channel), payload limits, and header casing. One page, linked from every endpoint reference, that an agent can ingest as the source of truth.