Skip to content

Register (or re-bind) a Web Push subscription

POST
/v1/push-subscriptions
curl --request POST \
--url https://api.aerandil.com/v1/push-subscriptions \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "app_url": "https://example.com", "declarative": false, "endpoint": "https://example.com", "expiration_time": "example", "keys": { "auth": "example", "p256dh": "example" }, "platform": "web", "token": "example", "user_agent": "example" }'

Register the calling staff member’s push endpoint — the PushSubscription.toJSON() shape plus the handset’s origin (app_url) and whether it subscribed through window.pushManager (declarative, Declarative Web Push). The (tenant, endpoint) pair is the identity: registering an endpoint again re-binds it to the caller, which is how a shared handset follows whoever signs in. Bound to the token’s Principal::User; a service credential is refused with 403. Accepts Idempotency-Key. The endpoint must be HTTPS (loopback HTTP in dev only).

Idempotency-Key
string
>= 1 characters

Opaque idempotency token (Stripe-shape). Repeated POSTs with the same key replay the original response within the cache TTL rather than creating a duplicate object.

Media typeapplication/json

A browser’s Web Push subscription (platform web, the default: endpoint, keys and app_url), or the Ithaca store app’s device token (platform apns / fcm: token only). A native platform the deployment does not send to is 404 push_not_configured.

object
app_url

web: the handset’s origin, e.g. https://ithaca.example/; the sender composes the notification’s navigate target from it.

string format: uri
declarative

Subscribed through window.pushManager (Declarative Web Push, Safari 18.4+); such messages are sent as application/notification+json.

boolean
endpoint

web: the push resource URL (RFC 8030).

string format: uri
expiration_time

Echoed by browsers; ignored.

keys

web: the user agent’s RFC 8291 key material.

object
auth
required

The 16-byte authentication secret, base64url.

string
p256dh
required

The user agent’s P-256 public key, base64url (65-byte uncompressed point).

string
platform

Where the push goes: a browser’s push service, Apple (iOS app) or Firebase Cloud Messaging (Android app).

string
default: web
Allowed values: web apns fcm
token

apns: the APNs device token (hex). fcm: the FCM registration token.

string
user_agent
string
<= 512 characters

Registered (or re-bound).

Media typeapplication/json
object
app_url

web only.

string format: uri
created_at
required
string format: date-time
declarative
required
boolean
endpoint
required

The push resource URL (web) or the device token (apns / fcm).

string
id
required
string format: uuid
idempotent_replay

Present and true when the response was replayed for a repeated Idempotency-Key.

boolean
last_delivered_at
string format: date-time
platform
required
string
Allowed values: web apns fcm
staff_member_id
required

The staff member the endpoint currently delivers to.

string format: uuid
tenant_id
required
string format: uuid
updated_at
required
string format: date-time
user_agent
string
Example
{
"platform": "web"
}
Cache-Control
string

Always no-store.

Location
string

/v1/push-subscriptions/{id}.

The bearer authenticated but does not hold the scope this operation requires.

Media typeapplication/json

Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.

object
error
required
object
code
required

Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).

string
instance_id

Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.

string
message
required

Human-readable explanation of the failure.

string
parameters

Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).

Examplegenerated
{
"error": {
"code": "example",
"instance_id": "example",
"message": "example",
"parameters": "example"
}
}

The request parsed but a validator refused it. The envelope’s code names which rule.

Media typeapplication/json

Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.

object
error
required
object
code
required

Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).

string
instance_id

Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.

string
message
required

Human-readable explanation of the failure.

string
parameters

Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).

Examplegenerated
{
"error": {
"code": "example",
"instance_id": "example",
"message": "example",
"parameters": "example"
}
}