---
title: "Put Push Transport"
method: PUT
path: "/v1/push-transport/{provider}"
tags: ["push-transport"]
---

# Put Push Transport

`PUT /v1/push-transport/{provider}`

## Request

- Base URL: `https://api.elaan.io` (the document declares no server; this is the origin it was published from)
- URL: `https://api.elaan.io/v1/push-transport/{provider}`
- Auth: none declared

Replace one provider's transport. Only that provider's entry is touched.

## Path parameters

- `provider` 'fcm' | 'apns' | 'expo' | 'onesignal' | 'webpush', required: The service a push subscription is delivered *through* — the routing key that ties a contact's `PushSubscription` to the tenant's configured `PushTransportConfig` and selects which sender adapter delivers it. Cross-cutting vocabulary (like `Channel`): it lives in `shared` because both `contact` (the destination side) and `push_transport` / `outbound_push` (the sending side) reference the same members, and they must not drift apart. Covers direct transports (FCM / APNs / Web Push) and aggregators that fan out to the native platforms themselves (Expo, OneSignal). Subclassing `str` keeps JSON and dict keys clean.

## Headers

- `authorization` string, nullable

## Request body

- PushTransportRequest: The settings for one provider. Which provider is in the **path** (`PUT /v1/push-transport/{provider}`), so it is no longer a body field — the resource identifies itself, and a body/path disagreement can't arise.
  - `expo` ExpoTransportInput
    - `access_token` string, required
  - `fcm` FcmTransportInput
    - `service_account_json` string, required
  - `webpush` WebPushTransportInput: The tenant's VAPID application server keys. Generated by the tenant (`npx web-push generate-vapid-keys`, `vapid --gen`), not by a provider console — Web Push has no account to sign up for. The pair is long-lived and **must not be rotated casually**: existing browser subscriptions were created against the public key and stop being deliverable if it changes, so a rotation means re-subscribing every browser.
    - `public_key` string, required
    - `private_key` string, required
    - `subject` string, required

## Response `200`

Successful Response

- PushTransportResponse: One configured transport. The no-config state is now an **empty list** from GET rather than a `provider: "none"` sentinel — with several transports possible per tenant, "none" was a statement the singleton shape needed and a collection expresses directly.
  - `provider` 'fcm' | 'apns' | 'expo' | 'onesignal' | 'webpush', required: The service a push subscription is delivered *through* — the routing key that ties a contact's `PushSubscription` to the tenant's configured `PushTransportConfig` and selects which sender adapter delivers it. Cross-cutting vocabulary (like `Channel`): it lives in `shared` because both `contact` (the destination side) and `push_transport` / `outbound_push` (the sending side) reference the same members, and they must not drift apart. Covers direct transports (FCM / APNs / Web Push) and aggregators that fan out to the native platforms themselves (Expo, OneSignal). Subclassing `str` keeps JSON and dict keys clean.
  - `expo` ExpoTransportView
    - `has_access_token` boolean, required
  - `fcm` FcmTransportView
    - `project_id` string, required
    - `client_email` string, required
    - `has_private_key` boolean, required
  - `webpush` WebPushTransportView: The public half is returned in full, deliberately: the console has to hand it to the browser as `applicationServerKey` for `pushManager.subscribe()`, and it is a public key. Only the private half is reduced to a `has_` boolean.
    - `public_key` string, required
    - `subject` string, required
    - `has_private_key` boolean, required
  - `updated_at` string, date-time, nullable
  - `credentials_unreadable` boolean: This provider has a stored transport whose credentials can no longer be decrypted (the encryption key was rotated), so its settings are absent from this entry. It is NOT the same as being unconfigured: sends through it fail until it is replaced. PUT the provider again with fresh credentials to repair it.

## Other responses

- `422`: The provider has no adapter yet, or its settings are malformed

---

[API](https://skmtc.dev/elaan/apis/elaan-api.md) · [All operations](https://skmtc.dev/elaan/apis/elaan-api/llms.txt) · [OpenAPI document](https://skmtc.dev/elaan/apis/elaan-api/revisions/c96a7e6399aa?raw)
