Build on JuhJuh
Build software that watches work happen and takes part in it. Events, permissions, delivery and rich messages for third-party apps.
Build on JuhJuh¶
JuhJuh is where work is described, worked on and shipped: tickets, chat, agent runs, repositories, pull requests. The platform API lets your own software watch that happen and take part in it.
- Creating and using an app — the end-to-end journey, both sides
- Events — everything you can be told about
- Scopes — what an app is allowed to do
- Transports — socket or webhook, close codes, signatures
- Cards — rich messages with buttons your app handles
- Maps — GeoJSON in chat, tickets and knowledge pages
- Compatibility promise — what is frozen
What you can build¶
Notifiers. Tell a room, a pager, or a spreadsheet when a ticket moves, a build breaks, or an agent finishes. The stream covers the whole lifecycle, so you can answer "what happened to this piece of work" end to end rather than stitching together three separate feeds.
Assistants in chat. Post a card with buttons, handle the click, update the card in place. The round-trip is a few lines and the result looks native.
Two-way integrations. Mirror tickets into another tracker, open a pull request when something is approved, comment back when a deploy lands. Your app gets its own identity, so its writes are attributed to it — not to whichever employee's credential it borrowed.
Reporting and audit. Read tickets, pull requests and reviews over the API on your own schedule and build whatever view your organisation needs.
Pick a transport¶
Both transports carry the identical JSON body, and an app can hold subscriptions on both. Choose on how your service is deployed, not on what it does.
| Socket | Webhook | |
|---|---|---|
| Who dials whom | your app connects out to us | we POST to your URL |
| Needs a public URL | no | yes, HTTPS |
| Needs an inbound firewall rule | no | yes |
| How you trust the payload | the connection is authenticated | verify the HMAC signature per request |
| While your app is down | frames are missed | retried for about 4 hours |
| Buttons on cards | handled on the same connection | needs a separate response path |
| Best for | long-running services, anything that can hold a connection | serverless functions, restricted egress, existing webhook plumbing |
Start with the socket. It has less to get wrong: no certificate, no public endpoint, no signature to verify, and card interactions come back on the same connection you are already reading. Move to webhooks when your app cannot hold a connection open — a function that only exists while it is handling a request, for example.
Neither transport is a database. Delivery is at-least-once and unordered. Events tell you that something happened; the REST API tells you what is true now. Every reliable integration reconciles against the API. See delivery semantics.
Five-minute quickstart¶
1. Get a token¶
An admin of your workspace opens Settings → Apps & Bots, creates an app,
grants it scopes, and copies the token. It starts with jjb_ and is shown
once — there is no way to read it again, only to issue a new one.
Grant the fewest scopes that work. An app that only posts to chat and reads
tickets asks for chat:write, tickets:read and events. Read never implies
write and there is no wildcard — see scopes.
2. Install the SDK¶
bash
pip install juhjuh
The SDK is optional. Everything below is plain HTTPS and a WebSocket, and the raw shapes are documented so you can implement them in any language.
3. Say hello¶
```python from juhjuh import JuhJuhClient
api = JuhJuhClient("jjb_example_not_a_real_token") print(api.me()) ```
You should see the app's own identity and the scopes it was granted. If this returns a 401, the token is wrong or was revoked; a 403 means the token is fine but lacks the scope for the call.
4. Listen¶
```python from juhjuh import SocketApp
app = SocketApp("jjb_example_not_a_real_token")
@app.on("ticket.status_changed") async def moved(event): ticket = event.data["ticket"] print(ticket["key"], "is now", ticket["status"])
app.run() ```
Nothing arrives until you subscribe:
python
api.create_subscription(events=["ticket.status_changed"], transport="socket")
An empty event list means no events, never all of them. Name what you want.
5. Say something¶
```python from juhjuh import cards
api.post_message( channel_id, card=cards.card( cards.header("Ready for review"), cards.section("JUJU-412 moved to review."), cards.actions(cards.button("Approve", action_id="approve", value="JUJU-412")), ), ) ```
Your app only sees channels it has been added to, and can only update messages it posted itself. Handling the button press is in cards.
Limits¶
| Limit | Value |
|---|---|
| Requests per app | 120 / minute |
| Events per app | 10,000 / hour |
Over the request limit you get 429 with a Retry-After header — wait the
number of seconds it names rather than retrying immediately. These numbers may
go up without notice; a reduction gets 90 days' notice.
Getting help¶
- developers@juhjuh.com — contract questions and breakage reports. Ask to join the breaking-change notification list.
- Delivery health is on the workspace's Apps & Bots page. It is the first place to look when deliveries stop: a subscription that failed for a full day is switched off automatically, and it says so there.