Creating and using an app
Developers

Creating and using an app

The end-to-end journey from both sides: the builder who creates an app and holds its token, and everyone else who works with it.

Creating an app, and using one

Two people, two jobs.

The builder creates an app in JuhJuh, gets a token, and writes the code that holds it. Usually an admin, usually once.

Everyone else just works with the app the way they work with a colleague: they add it to a channel, it posts, they click its buttons, it files tickets on their behalf.

This walks both sides end to end. For the API reference behind it, see Events, Scopes, Transports and Cards.


Part 1: create the app

1. Open the apps page

Settings → Apps, at /<org>/settings/apps/.

The page lists every app in the organisation with its scopes, its token prefix, and whether it is active or revoked. Apps live at the organisation level, not per project. One app can act across every project its scopes allow.

2. Create it

Create app asks for three things.

Name. What people see in chat next to its messages. Pick the product name, not the repository name.

Description. Up to 280 characters, shown to admins deciding whether to keep it. Say what it does and who runs it.

Scopes. Eight to choose from, and the choice matters more than anything else on the page.

Scope Grants
chat:read Read messages and threads in channels the app belongs to
chat:write Post messages and cards, update cards it posted
tickets:read Read tickets, comments and history
tickets:write Create and update tickets, comment, act as a delegate
repos:read Read branches, commits, tags, file contents
pulls:read Read pull requests, reviews and comments
pulls:write Open and update pull requests, submit reviews
events Subscribe to events and hold an event socket

Write scopes are shown separately in the picker so you can see at a glance what you are handing over.

Grant the smallest set that works. Scopes are editable afterwards from the app's detail page, so starting narrow costs you nothing. A notifier usually needs events plus one read scope. Nothing more.

3. Copy the token, once

Creating the app reveals a token beginning jjb_. That is the only time it exists in readable form. It is stored as a salted hash, it is not logged, and no one can recover it, including us.

Put it in whatever your service uses for secrets before you leave the page. If you lose it, revoke the app and create another. There is no reissue.

The jjb_ prefix is deliberate. Human API keys start jjk_, and the two are separate paths all the way down. An app can never inherit a person's permissions by holding their key, which is the entire reason app identities exist.

4. Subscribe to events

On the app's detail page, Subscriptions is where you choose what it hears about. Each subscription names a transport, an explicit list of events, and optionally one project to narrow it to.

Socket. Your app dials out to us and holds the connection. No public URL, no inbound firewall rule, no signature to verify. Card button clicks arrive on the same connection you are already reading.

Webhook. We POST to your HTTPS URL. You get a signing secret; verify the HMAC on every request. Failed deliveries retry for roughly four hours.

Start with the socket unless your service cannot hold a connection open.

There is no wildcard. You list the events you want, which means a new event type never starts arriving at an app that never asked for it.

5. Watch it work

Deliveries on the detail page shows recent attempts with their status. Any delivery can be replayed from there, which is the fastest way to debug a handler without waiting for the real thing to happen again.

Two properties to design around:

Delivery is at-least-once and unordered. The same event can arrive twice and a later one can arrive first. Every delivery carries a stable delivery_id that does not change across retries. Deduplicate on it.

Events are notifications, not a database. They tell you that something happened. The API tells you what is true now. Reconcile against the API before you act on anything that matters.

6. Connect your code

Authenticate with the token as a bearer credential. Confirm it works:

GET /api/v1/bot/me/ Authorization: Bearer jjb_...

That returns the app's identity, its organisation and its granted scopes, and it needs no scope of its own. If it returns 200, everything else is a matter of permissions.

The whole app surface, relative to /api/v1/:

Area Paths Scope
Identity bot/me/ none
Subscriptions bot/subscriptions/, bot/subscriptions/{id}/ events
Chat bot/channels/, bot/channels/{id}/messages/, bot/messages/{id}/card/ chat:read, chat:write
Projects orgs/{org}/projects/, projects/{key}/ any read scope
Tickets projects/{key}/tickets/ and its {id}/, comments/, transition/; tickets/by-key/{key}/ tickets:read, tickets:write
Pull requests projects/{key}/pulls/ and its comments/, reviews/, reviewers/ pulls:read, pulls:write
Repository reads projects/{key}/connections/{id}/browse/, commits/, tags/, compare/ repos:read

Repository routes are scoped to a connection because a repository is a remote service addressed through that connection, never a path on a box.

The published schema at /api/v1/schema/ contains this surface and nothing else. Internal endpoints are filtered out of it by an explicit allowlist, so what you can see there is what you are allowed to call.

Calls are limited to 120 per minute, and events to 10,000 per hour per organisation.


Part 2: another user uses the app

Nobody on the receiving end needs a token, a scope, or any of Part 1. From their side an app is a participant with a badge.

Add it to a channel

An app reads and posts only in channels it belongs to. Its read surface is what it was invited to, never the whole workspace. Adding it is how you grant that, and removing it is how you take it back.

Open the channel and click Apps in its header. Installed apps are listed first, with a Remove next to each; below them is everything else in the workspace that could work here.

The picker only offers apps holding chat:read or chat:write, since nothing else could act in a channel anyway. Both adding and removing are written to the audit log with who did it, and only members of the channel can install into it. That last part is the point: it stops anyone in the workspace from wiring a third party into a private channel they are not in themselves.

Apps cannot be added to direct messages, and an app never consumes a seat.

Add people is a separate button and still excludes apps deliberately. It is for inviting colleagues, and an app has no inbox to message.

What people see

An app's messages carry a small app tag next to the name, with a tooltip saying the account belongs to a third-party app. It renders with the app's own avatar.

That badge is load-bearing. A person, an AI agent and an app are three different kinds of author and they never render as each other. An app posting as though it were a colleague is the failure this feature exists to prevent.

Buttons and cards

Apps post cards: a message with structured content and buttons. Click one and the click goes to the app, which decides what to do and can update the card in place. Approve, reject, retry, assign, whatever the app defines.

Two things are true of every click:

The app is told who clicked. Actions are attributed to the person who took them, not to the app.

Authority is single-use. Each click hands the app a token good for that one interaction and a few minutes at most. It is redeemed on first use, so a replay of the same token does nothing, and one app can never present another app's token.

When an app acts on your behalf

An app filing a ticket for you sets itself as the delegate and leaves you as the assignee. The ticket shows both: you, an arrow, then the app, plus a screen-reader phrase saying it is acting on your behalf.

This is the distinction the whole design turns on. The work is still yours. The app is recorded as the thing that carried it out. Nobody reading the ticket later has to guess whether you filed it or something filed it for you.

If an app misbehaves

Tell an admin. From the app's page they can narrow its scopes, delete individual subscriptions, or revoke it outright.

Revoking is immediate and total: the token stops working, any open socket is closed, and every subscription it holds is dropped. There is no grace period and no partial state.


Quick reference

Admin screens

Screen Path
Apps list /<org>/settings/apps/
Create /<org>/settings/apps/create/
Detail, scopes, subscriptions, deliveries /<org>/settings/apps/<id>/

Facts worth remembering

  • The token is shown once and cannot be recovered
  • Scopes are editable, so grant narrow and widen later
  • An app only sees channels it was added to
  • Delivery is at-least-once and unordered; deduplicate on delivery_id
  • Revoking kills the token, the socket and the subscriptions together

Frozen, and safe to build on: scope names, event names, the app endpoint paths. New scopes and events can be added. Existing ones do not change meaning and are not removed. See the compatibility promise.