/v1/me/devicesStores the push token Apple handed the app, so the server can send it notifications. One registration per app per signed-in token: calling this again, for instance when iOS hands out a new push token, replaces the one before.
Needs no scope, the same as signing out. What
actually reaches the phone is still decided by the scopes of the token when there
is something to send: a token without mail:read gets no mail notifications,
registered or not.
Signing out ends the registration with it. So does a password change, which signs out every other device.
Request
curl -X POST https://api.proppertrading.com/v1/me/devices \
-H "Authorization: Bearer ptat_9f2c1a44_3b8e7d2f5c9a1b4e6d8f0a2c4e6b8d1f" \
-H "Idempotency-Key: 5b0e8c1c-9f0e-4d3e-a3b1-2f7c8d9e0a1b" \
-H "Content-Type: application/json" \
-d '{
"app": "mail",
"environment": "production",
"token": "8f3b2c1d9e0a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c",
"locale": "nl",
"timezone": "Europe/Amsterdam",
"app_version": "0.1.28",
"os_version": "27.0"
}'
| Field | Description |
|---|---|
app | mail, calendar or admin. Mail and the calendar share a token on a phone and register separately, because each has its own bundle id |
environment | sandbox for a development build, production for TestFlight and the App Store. A token sent to the other one is refused by Apple as if it did not exist |
token | The device token as hex |
locale | Optional. Not used for the words yet: those follow the account's language |
timezone | Optional, an IANA zone. A calendar reminder says the time on this clock |
Response
201 for a new registration, 200 for one that replaced the last.
{
"data": {
"object": "push_device",
"app": "mail",
"environment": "production",
"created_at": "2026-09-26T09:12:44.000000Z",
"last_seen_at": "2026-09-26T09:12:44.000000Z"
},
"meta": {
"request_id": "req_01kz9d2m3n4p5q6r7s8t9v0w1x"
}
}
What arrives
Every notification carries, beside the aps dictionary, a type and the
account it is for, so an app with several accounts opens the right one:
session is the id of the signed-in device it was sent to, the same id as in
the sessions list.
type | Extra fields |
|---|---|
mail.message | user_id, workspace, message_id, thread_id, mailbox_id |
calendar.reminder | user_id, workspace, event_id, occurrence_start, conference_url |
admin.notification | user_id, workspace, kind, notification_id, action_url |
test | none |
Every id in these fields is a string. Compare it with the ids the API hands out as it is, and never parse it as a number.
user_id is the id from your profile, such as
usr_hraj4wtnqgrkeu9b. workspace is the id from
the environments list: a propfirm's slug, or
platform.
message_id, thread_id and mailbox_id are the id, thread_id and
address_id of the message as a list row carries
them, such as msg_h3k8q2w7n4r9t5vx, so message_id is what
opening it takes. event_id is the id of the
appointment as the calendar hands it out.
Mail notifications are stacked per mailbox: the thread-id in aps is
mailbox- followed by the mailbox's id.
admin is Propper, which carries Mail and the calendar inside it. A device
registered as admin gets the mail and calendar notifications too, as long as
its token holds the scope to read them.
admin.notification goes to admin devices only, and is a notice from
the notification centre on its way to the
phone as well, in the same words: notification_id is its id there, kind
its type and action_url the same path. workspace is the environment the
reader runs, a propfirm's slug for its admins and platform for Propper's own
staff. These kinds arrive this way:
kind | When |
|---|---|
payout_requested | A payout waits to be reviewed, asked for or a cash prize |
payout_awaiting_payment | A payout was approved and waits to be paid |
affiliate_application_received | A trader applied to become an affiliate |
affiliate_code_requested | An affiliate asked for a code that has to be reviewed |
affiliate_payout_requested | An affiliate asked for their commission |
risk_flag_raised | A fraud detector raised a flag |
ticket_awaiting_admin | A support ticket waits for a person |
storage_filling_up | The environment is running out of storage |
mail_deliverability | The environment's mail bounces or draws complaints too often |
new_device_signed_in | Somebody signed in to your account on a new device |
deal_awaiting_approval | Staff: a new propfirm paid and waits for approval |
billing_payment_failed | Staff: a propfirm's invoice failed to collect |
deal_link_expired | Staff: a propfirm's onboarding link expired unused |
analytics_health | Staff: an environment's traffic looks wrong |
The first five go to the admins of the environment who may see that kind on
the dashboard, apart from whoever caused it. The rest go to whoever the bell
gives them to. An app should open action_url when it knows the screen and
fall back to the web version when it does not, as for the notification centre.
Mail notifications go for mail landing in an inbox, to everybody who may read that mailbox and has not switched it off, apart from its sender. The archive, spam and the rest stay quiet. They show sender and subject, unless the propfirm chose notifications without content. The badge is the unread count across the mailboxes this account can read in that environment.
Errors
| Status | Code | Meaning |
|---|---|---|
| 401 | invalid_token | Not a token, or the secret is wrong |
| 422 | validation_failed | A field is missing or has a bad shape |