How iOS push is configured per tenant, and how a mobile app registers to receive it.
When a message is posted in a room, Yama sends an APNs push to the mobile devices of every room member except the sender — unless the recipient has muted the conversation or their account. This guide has two parts: configuring APNs for a tenant (ops/admin) and integrating the mobile app (developers).
Push credentials are per tenant — each tenant ships its own iOS app, so it brings its own Apple key. Yama uses Apple's token-based authentication (a .p8 key), which does not expire and works for both environments.
In the admin back-office, open the tenant and fill the Push notifications (APNs) section:
| field | what | where to find it |
|---|---|---|
Auth key (.p8) | The APNs authentication key file. | Apple Developer → Certificates, Identifiers & Profiles → Keys → a key with the Apple Push Notifications service (APNs) capability. Download once. |
Key ID | 10-character key identifier. | Shown next to the key in the Keys list. |
Team ID | Apple Developer Team ID. | Apple Developer → Membership. |
Bundle ID (topic) | The app's bundle identifier; sent as the APNs apns-topic. | Your app target's bundle id, e.g. com.acme.chat. |
Environment | sandbox or production. | Use sandbox for Xcode/development builds, production for App Store / TestFlight. |
.p8 key is stored encrypted at rest and never shown again after upload. Re-uploading replaces it; leaving the file blank on edit keeps the current key. A tenant only starts receiving push once all of key, Key ID, Team ID and Bundle ID are set — otherwise the fan-out is silently skipped.Sandbox vs production must match the build: a token from a development build is rejected by the production gateway and vice-versa. Switch Environment accordingly when moving to the App Store.
After the OS grants push permission and hands you an APNs device token, register it so the server can reach the device. Authenticate with the same user JWT as the rest of the API (Authorization: Bearer <jwt>). Re-registering is idempotent.
POST /api/v1/devices
Authorization: Bearer <jwt>
Content-Type: application/json
{ "device": { "platform": "ios", "token": "<apns-device-token>" } }
// 201 Created
{ "id": 1, "user_id": "uuid", "platform": "ios", "token": "<…>", "created_at": "…" }
Unregister on logout or when the token rotates:
DELETE /api/v1/devices/<apns-device-token>
Authorization: Bearer <jwt>
// 200 OK
{ "token": "<…>", "registered": false }
platform defaults to ios; android is reserved for a future FCM channel.
A push is delivered on message_created to every member of the room except the author. It is an alert notification (title + body + sound), so it appears on a locked iPhone:
{
"aps": {
"alert": { "title": "<group name>", "body": "<sender>: <message preview>" },
"sound": "default"
}
}
muted flag: the account is in Do Not Disturb (status muted) or that room is muted for them. A timed mute stops suppressing on its own once it expires.So a user controls push the same way they control in-app notifications: by setting their status to Do Not Disturb (PATCH /api/v1/me) or muting a room (PUT /api/v1/rooms/:slug/mute).
Device tokens APNs reports as gone (410 Unregistered, BadDeviceToken) are deleted automatically server-side. Just re-register on the next launch; you never need to clean up stale tokens yourself.
Push is for when the app is in the background. While it is foregrounded, drive the UI from the live WebSocket feed instead, and use the per-event muted flag to decide whether to raise a local banner. See the real-time (WebSocket) guide for the full event catalogue.
| step | endpoint | auth |
|---|---|---|
| Register a device | POST /api/v1/devices | user JWT |
| Unregister a device | DELETE /api/v1/devices/:token | user JWT |
| Configure APNs (per tenant) | admin back-office → tenant → APNs | admin |