Seven endpoints, one header, no handshake. Everything an integration can do in joyn is on this page.
Create a key in space settings under Integrations, then confirm it works. If this returns 200, everything else on this page will too.
curl https://joyn-production-main-api.fly.dev/api/v1/integrations/me \
-H "Authorization: Bearer joyn_sk_your_key_here"
{
"id": 12,
"name": "Zapier",
"space": { "id": 3, "name": "Acme" },
"group": { "id": 7, "name": "Design" }
}Base URL https://joyn-production-main-api.fly.dev/api
Send the key as a bearer token on every request. There is no OAuth flow, no token exchange and no refresh.
Authorization: Bearer joyn_sk_7Kd2mQ...
Keys are shown once when created and stored only as a hash, so joyn cannot recover one for you. If a key is lost, regenerate it - the old one stops working the same second.
Where a key was created decides what it can reach. This is the single most common cause of an unexpected 403.
Space key
reaches The whole space
Created in space settings. Can invite members. Has no group memberships, so it cannot post until it is added to a group.
Group key
reaches One group
Created inside a group. Can post, create to-dos and create events in that group only. Cannot invite to the space.
Ids in lists are integers, message ids are strings
Message ids are 64-bit snowflakes returned as strings. Never parse one into a number: values above 2^53 lose precision and you will corrupt them silently.
{
"id": "7231904455168000"
}Every list is paginated the same way
List endpoints return rows plus a cursor. Pass the cursor back as next_page to continue. limit defaults to 100 and caps at 100.
{
"rows": [ ... ],
"has_next": true,
"next_page": 42
}Timestamps are ISO-8601 with a timezone
Send and expect full datetimes. A date-only value is rejected, so 2026-09-15 fails where 2026-09-15T14:00:00.000Z succeeds.
Omit optional fields, do not send them empty
An empty string is not a valid date or a valid note. Leave the property out entirely when you have no value for it.
Failures carry an error_code alongside the status, so you can branch on the code rather than parsing prose.
400
The body or query failed validation. Check formats before anything else.
401
The key is unknown, was regenerated, or its integration was deleted.
403
ROUTE_NOT_PERMITTED
Valid key, but this endpoint is not part of the api surface.
403
GROUP_SCOPED_KEY
A group key tried to do something space-level, such as inviting a member.
403
The key is not a member of the group, or the space withholds the permission the action needs.
404
The target does not exist, or is outside what this key can reach.
Anything not listed here returns 403 ROUTE_NOT_PERMITTED, even with a valid key. The surface is deliberately small.
/v1/integrations/me
integrations:read
Describes the key you are calling with. Use it to verify a connection and to learn the space id the space-level endpoints need.
{
"id": 12,
"name": "Zapier",
"space": { "id": 3, "name": "Acme" },
"group": { "id": 7, "name": "Design" }
}/v1/integrations/groups
groups:read
Groups this key can act in, read from its actual memberships. A space key with no group memberships gets an empty list.
Query
limit
1 to 100, defaults to 100.
next_page
Cursor from a previous response.
{
"rows": [
{ "id": 7, "name": "Design" }
],
"has_next": false
}/v1/integrations/group-channels
channels:read
Channels this key can reach. Filter by type to get only the ones a given action accepts: messages need MESSAGE or ANNOUNCEMENT, to-dos need TODO.
Query
type
Repeatable. MESSAGE, ANNOUNCEMENT, TODO or CHECKIN. Omit for all types.
limit
1 to 100, defaults to 100.
next_page
Cursor from a previous response.
GET /v1/integrations/group-channels?type=MESSAGE&type=ANNOUNCEMENT
{
"rows": [
{
"id": 42,
"name": "engineering",
"type": "MESSAGE",
"group": { "id": 7, "name": "Design" },
"created_at": "2026-07-30T09:00:00.000Z"
}
],
"has_next": false
}/v1/channels/:channel_id/messages
channels:write
Posts a message as the integration's bot. It appears in the channel immediately, marked as a bot, and deliberately does not send push notifications.
Body
content
required
Up to 1500 characters.
reply_to_message_id
Message id as a string, to reply inline.
curl -X POST https://joyn-production-main-api.fly.dev/api/v1/channels/42/messages \
-H "Authorization: Bearer joyn_sk_..." \
-H "Content-Type: application/json" \
-d '{ "content": "Deploy finished" }'
201 { "id": "7231904455168000" }/v1/channels/:channel_id/todos
todos:write
Adds a to-do to a TODO channel. Assigning requires joyn user ids, which the api does not expose yet, so leave assignees off.
Body
title
required
Up to 255 characters.
note
Up to 1000 characters. Omit rather than sending an empty string.
due_date
Date only, YYYY-MM-DD.
curl -X POST https://joyn-production-main-api.fly.dev/api/v1/channels/58/todos \
-H "Authorization: Bearer joyn_sk_..." \
-H "Content-Type: application/json" \
-d '{ "title": "Review the release notes", "due_date": "2026-09-15" }'/v1/spaces/:space_id/events
events:write
Creates an event on a group's calendar. group_id is optional in the schema but required in practice: without it the request needs a space permission keys do not hold.
Body
group_id
required
The group the event belongs to. From /v1/integrations/groups.
name
required
Up to 100 characters.
starts_at
required
ISO-8601 with a timezone.
ends_at
ISO-8601 with a timezone.
location
Up to 255 characters.
notify
Defaults to false. Leave it off for bulk imports.
curl -X POST https://joyn-production-main-api.fly.dev/api/v1/spaces/3/events \
-H "Authorization: Bearer joyn_sk_..." \
-H "Content-Type: application/json" \
-d '{
"group_id": 7,
"name": "Sprint review",
"starts_at": "2026-09-15T14:00:00.000Z",
"ends_at": "2026-09-15T15:00:00.000Z"
}'/v1/spaces/:space_id/email-invitations
space keys onlymembers:invite
Invites someone to the space by email. The invitation is single use and expires after seven days. Requires the space to grant CREATE_INVITATIONS, which it does by default.
Body
required
The address to invite.
curl -X POST https://joyn-production-main-api.fly.dev/api/v1/spaces/3/email-invitations \
-H "Authorization: Bearer joyn_sk_..." \
-H "Content-Type: application/json" \
-d '{ "email": "new@example.com" }'Things worth knowing before you build something load bearing on this.
Retries can duplicate writes
There are no idempotency keys yet. If a request times out but actually succeeded, retrying it creates a second message, to-do or event. Prefer triggers that fire once.
There are no triggers or webhooks
The api is one directional. Nothing in joyn can start an automation elsewhere, so a Zap cannot begin with a joyn event.
Keys never expire
A key works until you regenerate it or delete its integration. Rotate it on the same schedule you would rotate any other credential.
Rate limits are not published yet
Please stay well below anything that looks like a burst. Sensible limits will arrive, and they will be announced before they are enforced.
© 2026 Joyn. All rights reserved.
Made with ♥ for communities