← Integrations

API reference

Seven endpoints, one header, no handshake. Everything an integration can do in joyn is on this page.

Quickstart

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

Authentication

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.

Key scoping

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.

Conventions

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.

Errors

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.

Endpoints

Anything not listed here returns 403 ROUTE_NOT_PERMITTED, even with a valid key. The surface is deliberately small.

GET

/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" }
}
GET

/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
}
GET

/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
}
POST

/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" }
POST

/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" }'
POST

/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"
      }'
POST

/v1/spaces/:space_id/email-invitations

space keys only

members: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

email

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" }'

Limits and gaps

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.