Channel delegation (Sharing)
Delegation (sharing) is a way to give another person access to one of your channels without letting them into the rest of the account. A colleague will get either the right to only read messages from that channel, or to read and send new ones — but will not be able to see your other channels, change settings, or manage clients and tokens.
Useful when:
- you need to show the team only the
Prod-alertschannel without exposing the whole project; - a CI bot only needs to send to one channel (the
sendpermission) and nothing else; - the on-call person should see incoming notifications for a specific service (
view); - you want to grant access temporarily — a delegation can be easily revoked without touching the channel’s tokens.
Technically, each delegation is a separate Share token with the S
prefix, bound to a channel. The channel owner issues the token, optionally specifying
the recipient’s email and the permission level. The recipient uses the token like a regular
notifly token (see below How the recipient uses the access).
Access levels (permission)
Section titled “Access levels (permission)”permission | What is allowed |
|---|---|
view | See the channel in the list and read its messages (GET /message). |
send | Same as view, plus sending messages (POST /message). |
send includes view. Any mutations of the channel itself, clients, tokens,
as well as deleting messages using a Share token are forbidden (response 403).
Separately from the access level there is the right to share further
(canShare): it is off by default, and the recipient cannot hand your channel
out to others — see Re-sharing.
Invitation statuses (status)
Section titled “Invitation statuses (status)”A delegation to a specific email is first created as an invitation which the recipient can accept or decline:
status | What it means |
|---|---|
pending | The invitation has been issued; the recipient has not yet reacted. |
accepted | The recipient accepted — the channel has appeared in their list. |
declined | The recipient declined the invitation. |
Only a pending invitation can be accepted (POST /share/:id/accept); attempting
to accept/decline an already processed one will return 400 share is not pending.
How to share a channel
Section titled “How to share a channel”The request is made by the channel owner (or a delegated user — see Re-sharing) with an API token or a device token with write permission.
curl -X POST "$NOTIFLY_URL/application/42/share" \ -H "Content-Type: application/json" \ -H "X-Notifly-Key: <client-token>" \ -d '{ "recipientEmail": "colleague@example.com", "permission": "view", "note": "On-call this week" }'Request body fields (ChannelShareParams):
| Field | Type | Required | Description |
|---|---|---|---|
permission | string | yes | view or send. Any other value → 400. |
recipientEmail | string | no | Recipient’s email. If empty — becomes an “open share” (see below). |
canShare | bool | no | Allow the recipient to share the channel further (default false). |
note | string | no | Arbitrary comment for yourself (e.g. “for CI”, “for colleague X”). |
Response — the created ChannelShare object:
{ "id": 1007, "token": "S9f3kQw7Zx2Lm5Np8Rt1Vy4", "appid": 42, "recipientEmail": "colleague@example.com", "permission": "view", "canShare": false, "status": "pending", "note": "On-call this week", "created": "2026-06-23T10:00:00Z", "lastUsed": null, "appName": "Prod-alerts"}The token field (prefix S) is the Share token. Give it to the recipient
by any channel, or rely on them finding the invitation via
incoming.
If the addressee does not have a Notifly account yet, they get an invitation
email with a link to sign up with that address; after signing up the invitation
appears in their incoming list. Repeating the POST for the same address
re-sends the email (such a grant has canResendInvite: true in the response).
Granting again to someone who already has access to the channel does not create
a second row but changes the existing one: it raises the level to send,
changes canShare, returns a declined invitation to pending, and restores
revoked access — with a new token
value.
Open share (without email)
Section titled “Open share (without email)”If recipientEmail is left empty, the delegation becomes an open share —
a link/token that anyone who has it can use. In this case the recipient joins via
POST /share/join, and a personal copy of the delegation
(with its own Share token) is created for them, leaving the original untouched. This is convenient for invitations like “forward the link to the team”.
How the recipient uses the access
Section titled “How the recipient uses the access”After the delegation is accepted (accepted), the channel appears in the recipient’s normal list
(GET /application). Then the Share token is used like any notifly token:
# Read the channel's messages (view and send)curl "$NOTIFLY_URL/message" -H "X-Notifly-Key: S9f3kQw7Zx2Lm5Np8Rt1Vy4"
# Send to the channel (send only)curl -X POST "$NOTIFLY_URL/message" \ -H "X-Notifly-Key: S9f3kQw7Zx2Lm5Np8Rt1Vy4" \ -H "Content-Type: application/json" \ -d '{"title": "Deploy", "message": "Done"}'A Share token grants access strictly to a single channel. Attempts to call mutating
endpoints (create/modify a channel, delete a message, manage tokens) will return 403 channel tokens cannot perform this action.
Incoming invitations (for the recipient)
Section titled “Incoming invitations (for the recipient)”The recipient sees not-yet-accepted (pending) invitations to their email via
GET /share/incoming. Invitations to channels the recipient owns and
invitations with the same permissions as an already accepted delegation are not
shown; an invitation with different permissions is shown — it is an offer to
change the access level.
curl "$NOTIFLY_URL/share/incoming" -H "X-Notifly-Key: <client-token>"Accept or decline
Section titled “Accept or decline”# Accept — the channel will appear in the recipient's listcurl -X POST "$NOTIFLY_URL/share/1007/accept" -H "X-Notifly-Key: <client-token>"
# Declinecurl -X POST "$NOTIFLY_URL/share/1007/decline" -H "X-Notifly-Key: <client-token>"Only the user whose email matches the delegation’s recipientEmail can accept/decline; otherwise — 404. Only a pending invitation can be processed.
Joining by token
Section titled “Joining by token”If the recipient was given the Share token directly (for example, an open share or simply the S... string), they join with a single call:
curl -X POST "$NOTIFLY_URL/share/join" \ -H "Content-Type: application/json" \ -H "X-Notifly-Key: <client-token>" \ -d '{"token": "S9f3kQw7Zx2Lm5Np8Rt1Vy4"}'Behavior depends on the type of delegation:
- Named (with
recipientEmail): the token joins only if the user’s email matches the delegation’s address (otherwise403 this share is not for your email). The status is automatically changed toaccepted. - Open share (without email): a personal copy of the delegation is created for the joiner with status
accepted. If they already have a delegation for this channel, it is upgraded tosendif necessary (and getscanShareif the link grants it) and set toaccepted. If the owner revoked their access to the channel, they cannot come back via the link:403 access to this channel was revoked by the owner.
An invalid, revoked or expired token → 404 invalid share token. The channel owner cannot join their own channel → 409 you already own this channel.
Managing issued delegations (for the owner)
Section titled “Managing issued delegations (for the owner)”List of delegations for a specific channel. The channel owner sees all grants, including those made by recipients through re-sharing (other people’s grants have createdByEmail filled in); a recipient with canShare sees only their own:
curl "$NOTIFLY_URL/application/42/share" -H "X-Notifly-Key: <client-token>"Change the permission level, the right to share further, the email or the note — PUT /share/:id (channel owner only). If the level of an accepted delegation changes, the recipient gets a notification (“Your access was raised” / “Channel access changed”):
curl -X PUT "$NOTIFLY_URL/share/1007" \ -H "Content-Type: application/json" \ -H "X-Notifly-Key: <client-token>" \ -d '{"permission": "send", "canShare": false, "recipientEmail": "colleague@example.com"}'Revoke a delegation — DELETE /share/:id. This can be done by its creator or
by the channel owner. Revocation is two-step:
- the first
DELETEon an active delegation revokes it: the token stops working immediately and the channel disappears from the recipient’s list, while the row stays in the list (withrevokedAt) and acts as a block — the recipient cannot come back via a still-live open link. Together with the access, the recipient’s “ownerless” grants for this channel are revoked too: open links and unaccepted invitations; - a repeated
DELETEon an already revoked row deletes it for good — together with the block. To close entry via an open link for good, delete the link itself.
Revoked access can be restored by granting again to the same addressee — the token will be new.
curl -X DELETE "$NOTIFLY_URL/share/1007" -H "X-Notifly-Key: <client-token>"To avoid typing emails manually, the sharing form can suggest addresses you’ve already shared with — GET /share/recipients returns a list of distinct emails from newest to oldest:
curl "$NOTIFLY_URL/share/recipients" -H "X-Notifly-Key: <client-token>"Leaving and rotating your own token (for the recipient)
Section titled “Leaving and rotating your own token (for the recipient)”The recipient manages only their own side of the access — with two calls. Both
take the channel in the request body (appid) rather than in the path:
# Give up access to the channel (it disappears from the list, the token dies)curl -X POST "$NOTIFLY_URL/share/leave" \ -H "Content-Type: application/json" \ -H "X-Notifly-Key: <client-token>" \ -d '{"appid": 42}'
# Change the value of your own Share token (for example, if it leaked)curl -X POST "$NOTIFLY_URL/share/rotate" \ -H "Content-Type: application/json" \ -H "X-Notifly-Key: <client-token>" \ -d '{"appid": 42}'share/leaveremoves all of your accesses to that channel (there may be several if different people shared it). To come back you need a new invitation — or a still-valid open link.share/rotatechanges the token value in place, keeping the permissions and the binding; the old value stops working immediately. All of your accepted and valid delegations for that channel are rotated; a revoked one cannot be rotated.
If you have no access to the channel, both calls return 404.
Machine keys of a channel
Section titled “Machine keys of a channel”Email delegations are about people. For scripts, CI and integrations a channel has
machine keys: several keys per channel, each with its own permissions, label,
expiry and its own lastUsed.
# Issue a send-only keycurl -X POST "$NOTIFLY_URL/application/42/token" \ -H "Content-Type: application/json" \ -H "X-Notifly-Key: <client-token>" \ -d '{"name": "CI deploy key", "canRead": false, "canWrite": true, "note": "gitlab runner", "expiresAt": 0}'| Field | Meaning |
|---|---|
name | human-readable label of the key |
canRead | reading the channel history and subscribing over WebSocket |
canWrite | sending to the channel (POST /message, /ask) |
note | free-form comment |
expiresAt | expiry, unix milliseconds; 0 — never |
A key must allow at least one of canRead/canWrite, otherwise 400. Key
management is available only to the channel owner: a delegation recipient does
not get it even with write permission.
Revocation has two steps. The first DELETE /token/:id is soft: the key stops
authenticating, but the row remains, so you can still see when it was last used
and when it was revoked. A repeated DELETE removes the record permanently.
Rotation (POST /token/:id/rotate) changes only the value: the id, the
permissions and the channel binding are preserved, so nothing that references the
key breaks.
Revocation takes effect from the next request. The exception is an already open WebSocket connection: it was authorised before the upgrade and lives with the old key until it reconnects.
Re-sharing (delegating further)
Section titled “Re-sharing (delegating further)”Only a recipient whom the owner granted the canShare right can share the
channel further; without it POST /application/:id/share returns
403 you are not allowed to share this channel further. Even with this right
you cannot grant more than you have yourself. If you only have view,
attempting to grant send will return:
403 cannot grant 'send' permission — you only have 'view' accessThe canShare right itself can likewise be passed on only by someone who has
it.
The channel owner is always considered to have maximum access (send and
canShare). In the GET /application/:id/share list a re-sharer sees only
their own grants, while the owner sees all of them, with who made each one
(createdByEmail), and can revoke any of them.
REST API
Section titled “REST API”| Method and path | Authorization | Purpose |
|---|---|---|
GET /application/:id/share | client-token | list of channel delegations (only your own) |
POST /application/:id/share | client-token (write) | create a delegation (issue a Share token) |
PUT /share/:id | client-token (write) | change permission / email / note (channel owner) |
DELETE /share/:id | client-token (write) | revoke a delegation |
GET /share/incoming | client-token | incoming invitations to the user’s email |
GET /share/recipients | client-token | email addresses you’ve already shared with (for suggestions) |
POST /share/:id/accept | client-token | accept a pending invitation |
POST /share/:id/decline | client-token | decline a pending invitation |
POST /share/join | client-token | join a channel by Share token |
POST /share/leave | client-token (write) | give up access to a channel, body {"appid": N} |
POST /share/rotate | client-token (write) | change your own Share token value, body {"appid": N} |
GET /application/:id/token | client-token | machine keys of the channel (owner only) |
POST /application/:id/token | client-token (write) | issue a machine key |
PUT /token/:id | client-token (write) | change permissions, label and expiry |
DELETE /token/:id | client-token (write) | revoke a key (a repeated call deletes it permanently) |
POST /token/:id/rotate | client-token (write) | change the key value, keeping its permissions |