Guildbeta

Developers

Everything here is the API the app itself uses. Every call is a plain HTTP request with a bearer token, there is no SDK to install, and anything that can send JSON can be an integration.

Get started#

The API is pre-1.0. It is in use every day, but paths, fields and shapes still change when they need to - so pin the version you build against, and read the reference below as what the API answers today.

Get a key#

A key is how a script, a bot or an assistant acts as you. Mint one in the app - your profile, then the keys tab - and give it a name you will recognise later. It is the whole of the authentication on every call below: send it as Authorization: Bearer <key>.

# From the app: your profile -> keys. It is shown once.
export GUILD_KEY="gk_live_..."

# Worth knowing before you start:
#   it acts as you - the same channels, and nothing more
#   keep it out of a repository; revoke it if it leaks
#   a key belongs to one workspace, so two means two keys

Set the ids the examples use#

Every example calls the API's own address, so there is nothing to configure there. The four ids below are what the later calls fill in: the walkthrough hands each one back, so read it in order and they will be right.

# https://api.useguild.dev is the API's address - no variable to set.

# Filled in as you go, so the examples run as written:
export CHANNEL_ID=""   # from GET /api/channels
export MESSAGE_ID=""   # from the task you create
export CURSOR=""       # from the X-Next-Cursor header

The path an integration takes#

Seven calls, in the order they make sense. Read them in order and you have a working integration; jump to the one you need and the example runs on its own.

Find the channels you can read#

Everything in Guild lives in a channel. This is the list the caller is a member of - the tokens are scoped to one workspace, so there is nothing to pass but the token itself.

curl -s https://api.useguild.dev/api/channels \
  -H "Authorization: Bearer $GUILD_KEY"
[
  { "_id": "6aaff2e8359000011551b360", "name": "incidents",
    "kind": "public", "key": "INC", "members": [ "..." ] }
]

Read a channel's messages#

Oldest first, and paged: the answer carries the next cursor in the X-Next-Cursor header. Pass it back as since to keep reading forwards, or as before to read backwards. A task is in this same list - a task is a message, printed in a structured way.

curl -s "https://api.useguild.dev/api/channels/$CHANNEL_ID/messages?limit=50" \
  -H "Authorization: Bearer $GUILD_KEY" \
  -D -   # the X-Next-Cursor header comes out here
[
  { "_id": "6aaff2e8359000011551b361", "text": "the queue is draining again",
    "author_id": "6aaff2e8359000011551b362",
    "written_at": "2026-09-28T09:00:00Z" }
]

Post a message#

One call writes either a plain message or a task, because they are the same thing stored two ways: type says which. The answer is the stored message, so you have its id without asking again.

curl -s https://api.useguild.dev/api/channels/$CHANNEL_ID/messages \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"message","text":"Deploying 4f21c0a now."}'
{
  "_id": "6aaff2e8359000011551b361",
  "type": "message",
  "text": "Deploying 4f21c0a now."
}

Turn a conversation into a task#

This is the thing Guild is for. Pick the messages that contain the decision, and name the work: the task is created from them and stays joined to the conversation it came from, so whoever picks it up can read why it exists.

curl -s https://api.useguild.dev/api/channels/$CHANNEL_ID/tasks \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message_ids":["6aaff2e8359000011551b361"],
       "title":"Ship the queue fix",
       "description":"before the standup"}'
{
  "_id": "6aaff2e8359000011551b363",
  "type": "task",
  "title": "Ship the queue fix",
  "status": "todo",
  "key": "INC-1"
}

Move it along#

A task is in exactly one of five statuses, and moving it is its own call: todo, backlog, in_progress, done, discarded. This is what a board, a bot or a person at a keyboard all do the same way.

curl -s https://api.useguild.dev/api/messages/$MESSAGE_ID/status \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"in_progress"}'
{
  "_id": "6aaff2e8359000011551b363",
  "type": "task",
  "status": "in_progress"
}

Hand it to somebody#

Pass a member's id and the task is theirs - which is what makes "who is on this" a fact rather than a convention.

curl -s https://api.useguild.dev/api/messages/$MESSAGE_ID/assignee \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"user_id":"6aaff2e8359000011551b362"}'
{
  "_id": "6aaff2e8359000011551b363",
  "assignee_id": "6aaff2e8359000011551b362"
}

Search everything at once#

One query looks across messages, tasks, commits, pull requests, runs and channels, by meaning as well as by word. What you may find is decided by your membership, never by the query - there is no way to search your way into a channel you are not in.

curl -s "https://api.useguild.dev/api/search?q=queue+in:incidents+since:2026-09-01" \
  -H "Authorization: Bearer $GUILD_KEY"
{
  "results": [
    { "kind": "message", "channel_id": "...", "thread_root": null,
      "snippet": "the queue is draining again", "score": 0.83 }
  ]
}

Your own webhooks#

The other direction: Guild tells you. Add a hook to a channel or to the whole workspace, give it a URL and a secret, choose what it should hear, and Guild POSTs one JSON body per event.

What a delivery looks like#

The headers first: the secret is the whole of the authentication (a bearer credential, not a signature), and the two X-Guild-* headers are there so you can route without parsing the body.

POST /your/endpoint HTTP/1.1
authorization: Bearer <the secret you set on the hook>
content-type: application/json
x-guild-event: task.status_changed
x-guild-delivery: 6aaff2e8359000011551b370
user-agent: guild/0.20.1
{
  "id": "6aaff2e8359000011551b370",
  "event": "task.status_changed",
  "workspace_id": "6aaff2e8359000011551b35e",
  "at": "2026-09-28T09:00:00Z",
  "actor_id": "6aaff2e8359000011551b362",
  "channel_id": "6aaff2e8359000011551b360",
  "payload": {
    "_id": "6aaff2e8359000011551b363",
    "type": "task",
    "title": "Ship the queue fix",
    "status": "in_progress"
  }
}

What you owe it#

Answer 2xx, and answer quickly#

A 2xx is the whole of "delivered". Anything else is a rejected attempt, with the status and the first part of what you said recorded against it; no answer at all is unreachable.

Nothing is retried for you#

Guild does not retry a delivery. A receiver that wants one keeps the X-Guild-Delivery id and asks again in its own time - or re-reads the API, which is the same data and the only thing that heals a gap.

You can see whether you are the problem#

Every attempt is a row in the workspace's delivery log with its outcome (delivered, rejected or unreachable), the status and the reason - so an endpoint that has been refusing everything for a week looks different from one that is working.

Adding one#

# Add one: the URL to POST to, the secret to sign it with, the events to hear.
# Leave `channel_id` out and the hook hears from the whole workspace; give it a
# channel's id and it hears from that channel alone. No `events` at all means every
# kind - which is the API's way of saying it, not a hook that hears nothing.
curl -s https://api.useguild.dev/api/workspace/hooks \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/guild",
       "secret":"a long random string",
       "channel_id":"6aaff2e8359000011551b360",
       "events":["task.status_changed","message.created"]}'

# What it has been saying: one row per attempt, newest first.
curl -s https://api.useguild.dev/api/workspace/hooks/$HOOK_ID/deliveries \
  -H "Authorization: Bearer $GUILD_KEY"

What a hook can hear#

Every kind below, in the app's own words. Choose none and the hook hears everything; choose some and it hears those.

Messagesmessage.created
Message editsmessage.modified
Tasks createdtask.created
Task status changestask.status_changed
Summariessummary.requested
Channels createdchannel.created
People joiningchannel.joined
People leavingchannel.left
Invitationsinvite.created
Invitations acceptedinvite.accepted
Invitations declinedinvite.declined
Accounts registereduser.registered
Settings changesuser.configured
Workspace changesworkspace.configured

The reference#

Guild API 0.19.0, as this site was built against it. Grouped the way an integrator meets it: the areas the walkthrough above moves through, and what else there is to call. The sidebar lists them, so nothing here is reachable only by scrolling.

Channels#

23 calls

The rooms a team shares: finding them, joining them, and what is inside one.

GET/api/channels

The channels the caller is a member of.

curl -s https://api.useguild.dev/api/channels \
  -H "Authorization: Bearer $GUILD_KEY"
[
  {
    "_id": "6aaff2e8359000011551b35f",
    "created_at": "2026-09-28T09:00:00Z",
    "created_by": "string",
    "deleted_at": "2026-09-28T09:00:00Z",
    "dm_key": "string",
    "key": "GEN-1",
    "kind": "channel",
    "members": [
      {
        "role": "admin",
        "user_id": "6aaff2e8359000011551b362"
      }
    ],
    "name": "incidents",
    "quarantined_until": "2026-09-28T09:00:00Z",
    "task_seq": 0,
    "visibility": "public",
    "workspace_id": "6aaff2e8359000011551b35e"
  }
]
POST/api/channels

Create a channel. The creator becomes its first member.

visibility decides how others get in: a public (the default) channel can be joined directly, a private one only by invitation.

curl -s https://api.useguild.dev/api/channels \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"incidents","visibility":"public"}'
{
  "name": "incidents",
  "visibility": "public"
}
{
  "_id": "6aaff2e8359000011551b35f",
  "created_at": "2026-09-28T09:00:00Z",
  "created_by": "string",
  "deleted_at": "2026-09-28T09:00:00Z",
  "dm_key": "string",
  "key": "GEN-1",
  "kind": "channel",
  "members": [
    {
      "role": "admin",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "name": "incidents",
  "quarantined_until": "2026-09-28T09:00:00Z",
  "task_seq": 0,
  "visibility": "public",
  "workspace_id": "6aaff2e8359000011551b35e"
}
GET/api/channels/deleted

List the deleted channels

Administrative, and admin-only: the channels of this workspace that were deleted and may be purged for good. Most recently deleted first.

curl -s https://api.useguild.dev/api/channels/deleted \
  -H "Authorization: Bearer $GUILD_KEY"
[
  {
    "_id": "6aaff2e8359000011551b35f",
    "created_at": "2026-09-28T09:00:00Z",
    "created_by": "string",
    "deleted_at": "2026-09-28T09:00:00Z",
    "dm_key": "string",
    "key": "GEN-1",
    "kind": "channel",
    "members": [
      {
        "role": "admin",
        "user_id": "6aaff2e8359000011551b362"
      }
    ],
    "name": "incidents",
    "quarantined_until": "2026-09-28T09:00:00Z",
    "task_seq": 0,
    "visibility": "public",
    "workspace_id": "6aaff2e8359000011551b35e"
  }
]
DELETE/api/channels/deleted/{channel_id}

Purge a deleted channel

Administrative, and admin-only, and only for a channel that is already deleted - a live one has to be deleted first, so a stray call cannot destroy a conversation. The channel's messages are removed with it, for good; nothing is recoverable afterwards.

curl -s https://api.useguild.dev/api/channels/deleted/6aaff2e8359000011551b360 \
  -X DELETE \
  -H "Authorization: Bearer $GUILD_KEY"
GET/api/channels/discover

List the public channels

Every public channel, oldest first. Private channels are never listed. An external member is refused: they enter a channel by invitation, so the list of what exists here is not theirs to read.

curl -s https://api.useguild.dev/api/channels/discover \
  -H "Authorization: Bearer $GUILD_KEY"
[
  {
    "_id": "6aaff2e8359000011551b35f",
    "created_at": "2026-09-28T09:00:00Z",
    "created_by": "string",
    "deleted_at": "2026-09-28T09:00:00Z",
    "dm_key": "string",
    "key": "GEN-1",
    "kind": "channel",
    "members": [
      {
        "role": "admin",
        "user_id": "6aaff2e8359000011551b362"
      }
    ],
    "name": "incidents",
    "quarantined_until": "2026-09-28T09:00:00Z",
    "task_seq": 0,
    "visibility": "public",
    "workspace_id": "6aaff2e8359000011551b35e"
  }
]
GET/api/channels/{channel_id}

One channel, if the caller is a member.

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360 \
  -H "Authorization: Bearer $GUILD_KEY"
{
  "_id": "6aaff2e8359000011551b35f",
  "created_at": "2026-09-28T09:00:00Z",
  "created_by": "string",
  "deleted_at": "2026-09-28T09:00:00Z",
  "dm_key": "string",
  "key": "GEN-1",
  "kind": "channel",
  "members": [
    {
      "role": "admin",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "name": "incidents",
  "quarantined_until": "2026-09-28T09:00:00Z",
  "task_seq": 0,
  "visibility": "public",
  "workspace_id": "6aaff2e8359000011551b35e"
}
DELETE/api/channels/{channel_id}

Delete a channel

A moderator's act: a mod or admin of this channel, or of the workspace. The channel disappears from every listing and refuses reads and writes, but its rows are kept: an administrator may list the deleted channels and purge one for good. A direct conversation cannot be deleted. Deleting an already-deleted channel is a no-op.

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360 \
  -X DELETE \
  -H "Authorization: Bearer $GUILD_KEY"
GET/api/channels/{channel_id}/agent

Read a channel's agent

Administrative: an admin of this channel, or any workspace admin. The name, the system prompt and the two triggers. A channel with no agent answers 404 - which is the normal case, not an error to recover from.

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360/agent \
  -H "Authorization: Bearer $GUILD_KEY"
{
  "allow_external": true,
  "channel_id": "6aaff2e8359000011551b360",
  "description_prompt": "string",
  "id": "6aaff2e8359000011551b35f",
  "name": "ada-bot",
  "on_mention": true,
  "on_new_messages": true,
  "plugins": [
    {
      "all_agents": true,
      "kind": "guild",
      "name": "incidents",
      "plugin_id": "6aaff2e8359000011551b35f",
      "settings": {}
    }
  ],
  "prefill_tasks": true,
  "system_prompt": "You answer questions about the ingest pipeline. Be brief.",
  "title_prompt": "string"
}
PUT/api/channels/{channel_id}/agent

Configure a channel's agent

Administrative: an admin of this channel, or any workspace admin. A full replacement of the configuration. name is also the mention token (@name), so it has no spaces. on_new_messages answers a new top-level message and never one inside a thread; on_mention answers @name anywhere.

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360/agent \
  -X PUT \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"allow_external":true,"description_prompt":"Describe what has to happen, and who it affects. Use Gherkin (Given/When/Then) when it fits. Keep it under 200 words.","name":"ada-bot","on_mention":true,"on_new_messages":true,"plugins":[{"plugin_id":"507f1f77bcf86cd799439011","settings":{"channels":"devprod"}}],"prefill_tasks":true,"system_prompt":"You answer questions about the ingest pipeline. Be brief.","title_prompt":"Name the work the messages describe: a short, imperative title of at most 20 words, with no trailing period."}'
{
  "allow_external": true,
  "description_prompt": "Describe what has to happen, and who it affects. Use Gherkin (Given/When/Then) when it fits. Keep it under 200 words.",
  "name": "ada-bot",
  "on_mention": true,
  "on_new_messages": true,
  "plugins": [
    {
      "plugin_id": "507f1f77bcf86cd799439011",
      "settings": {
        "channels": "devprod"
      }
    }
  ],
  "prefill_tasks": true,
  "system_prompt": "You answer questions about the ingest pipeline. Be brief.",
  "title_prompt": "Name the work the messages describe: a short, imperative title of at most 20 words, with no trailing period."
}
{
  "allow_external": true,
  "channel_id": "6aaff2e8359000011551b360",
  "description_prompt": "string",
  "id": "6aaff2e8359000011551b35f",
  "name": "ada-bot",
  "on_mention": true,
  "on_new_messages": true,
  "plugins": [
    {
      "all_agents": true,
      "kind": "guild",
      "name": "incidents",
      "plugin_id": "6aaff2e8359000011551b35f",
      "settings": {}
    }
  ],
  "prefill_tasks": true,
  "system_prompt": "You answer questions about the ingest pipeline. Be brief.",
  "title_prompt": "string"
}
DELETE/api/channels/{channel_id}/agent

Remove a channel's agent

Administrative: an admin of this channel, or any workspace admin. The agent stops answering immediately. The messages it already wrote keep their author, so the user it spoke as is kept - only the agent is removed.

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360/agent \
  -X DELETE \
  -H "Authorization: Bearer $GUILD_KEY"
GET/api/channels/{channel_id}/agent/plugins

List the plugins a channel's agent may use

Administrative: an admin of this channel, or any workspace admin. Every plugin configured in the workspace that this channel may bind, and whether its agent already does - the list the form is built from.

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360/agent/plugins \
  -H "Authorization: Bearer $GUILD_KEY"
[
  {
    "all_agents": true,
    "bound": true,
    "id": "6aaff2e8359000011551b35f",
    "kind": "guild",
    "name": "incidents",
    "tools": [
      "string"
    ]
  }
]
POST/api/channels/{channel_id}/attachments

Upload an attachment into a channel

Any member of the channel. The body is the file itself with its Content-Type; the name is in ?filename=. An image is scaled to fit 1920x1080 and served inline; anything else is stored zip-wrapped under that name and served as a download. At most 25 MB.

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360/attachments \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '[0]'
[
  0
]
{
  "_id": "6aaff2e8359000011551b35f",
  "bytes": 0,
  "content_type": "string",
  "created_at": "2026-09-28T09:00:00Z",
  "filename": "string",
  "height": 0,
  "kind": "image",
  "uncompressed_bytes": 0,
  "url": "/api/attachments/507f1f77bcf86cd799439012",
  "width": 0
}
POST/api/channels/{channel_id}/invites

Invite somebody to a channel

The invitee answers it through GET /api/invites and POST /api/invites/{invite_id}/accept. This is how a private channel is entered, and the only way a member whose workspace role is external enters any channel.

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360/invites \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"teammate@example.com","role":"admin"}'
{
  "email": "teammate@example.com",
  "role": "admin"
}
{
  "_id": "6aaff2e8359000011551b35f",
  "channel_id": null,
  "created_at": "2026-09-28T09:00:00Z",
  "invited_by": "string",
  "invitee_email": "string",
  "invitee_id": null,
  "role": "admin",
  "status": "pending",
  "workspace_id": "6aaff2e8359000011551b35e"
}
POST/api/channels/{channel_id}/join

Join a public channel

A private channel cannot be joined this way - it is entered by accepting an invitation (see POST /api/invites/{invite_id}/accept). A member whose workspace role is external cannot join this way either: an external person enters a channel by invitation only, private or public.

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360/join \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY"
{
  "_id": "6aaff2e8359000011551b35f",
  "created_at": "2026-09-28T09:00:00Z",
  "created_by": "string",
  "deleted_at": "2026-09-28T09:00:00Z",
  "dm_key": "string",
  "key": "GEN-1",
  "kind": "channel",
  "members": [
    {
      "role": "admin",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "name": "incidents",
  "quarantined_until": "2026-09-28T09:00:00Z",
  "task_seq": 0,
  "visibility": "public",
  "workspace_id": "6aaff2e8359000011551b35e"
}
DELETE/api/channels/{channel_id}/members/me

Leave a channel

Removes the caller from the channel's members. The channel and its messages are untouched, and the caller can join again if it is public. me is the authenticated user, so nobody else can be removed this way.

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360/members/me \
  -X DELETE \
  -H "Authorization: Bearer $GUILD_KEY"
DELETE/api/channels/{channel_id}/members/{user_id}

Remove a member from a channel

Allowed to an admin of that channel, or to any workspace admin. The target must already be a member (404 otherwise), the caller may not remove themselves (409 - leaving is a member's own act, DELETE /api/channels/{channel_id}/members/me), and a direct conversation cannot be administered at all (422).

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360/members/6aaff2e8359000011551b362 \
  -X DELETE \
  -H "Authorization: Bearer $GUILD_KEY"
PUT/api/channels/{channel_id}/members/{user_id}/role

Set a channel member's role

Allowed to an admin of that channel, or to any workspace admin. The target must already be a member. The last channel admin cannot be demoted (409).

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360/members/6aaff2e8359000011551b362/role \
  -X PUT \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"role":"admin"}'
{
  "role": "admin"
}
{
  "_id": "6aaff2e8359000011551b35f",
  "created_at": "2026-09-28T09:00:00Z",
  "created_by": "string",
  "deleted_at": "2026-09-28T09:00:00Z",
  "dm_key": "string",
  "key": "GEN-1",
  "kind": "channel",
  "members": [
    {
      "role": "admin",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "name": "incidents",
  "quarantined_until": "2026-09-28T09:00:00Z",
  "task_seq": 0,
  "visibility": "public",
  "workspace_id": "6aaff2e8359000011551b35e"
}
GET/api/channels/{channel_id}/messages

List the top level messages and tasks of a channel

Returns at most limit messages, oldest first. The next cursor is in the X-Next-Cursor response header: pass it back as since to keep reading forwards, or as before to read the page of history above what you have. Replies are not listed here: read a message's replies through GET /api/messages/{message_id}/thread.

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360/messages \
  -H "Authorization: Bearer $GUILD_KEY"
[
  {
    "_id": "6aaff2e8359000011551b35f",
    "attachments": [
      {
        "_id": "6aaff2e8359000011551b35f",
        "bytes": 0,
        "content_type": "string",
        "created_at": "2026-09-28T09:00:00Z",
        "filename": "string",
        "height": 0,
        "kind": "image",
        "uncompressed_bytes": 0,
        "url": "/api/attachments/507f1f77bcf86cd799439012",
        "width": 0
      }
    ],
    "author_id": "6aaff2e8359000011551b35f",
    "channel_id": "6aaff2e8359000011551b360",
    "context_of": null,
    "created_at": "2026-09-28T09:00:00Z",
    "depth": 0,
    "from_agent": true,
    "parent": null,
    "reactions": [
      {
        "emoji": "string",
        "user_id": "6aaff2e8359000011551b362"
      }
    ],
    "root_id": null,
    "updated_at": "2026-09-28T09:00:00Z",
    "workspace_id": "6aaff2e8359000011551b35e",
    "text": "Deployed 4f21c0a - the queue is draining again.",
    "type": "message",
    "permalink": "https://app.example.com/c/507f1f77bcf86cd799439011/m/507f1f77bcf86cd799439012"
  }
]
POST/api/channels/{channel_id}/messages

Post a message, or a task, into a channel

A plain message, or a task. A task is offered the workspace's tag catalog as it is created: it comes back carrying the tags Jev was at least 90% confident about, applied by the server, and it is created the same way when that judgement does not arrive.

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360/messages \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"attachments":["507f1f77bcf86cd799439012"],"text":"we should ship it","type":"message","parent":null}'
{
  "attachments": [
    "507f1f77bcf86cd799439012"
  ],
  "text": "we should ship it",
  "type": "message",
  "parent": null
}
{
  "text": "Deployed 4f21c0a - the queue is draining again.",
  "_id": "6aaff2e8359000011551b35f",
  "attachments": [
    {
      "_id": "6aaff2e8359000011551b35f",
      "bytes": 0,
      "content_type": "string",
      "created_at": "2026-09-28T09:00:00Z",
      "filename": "string",
      "height": 0,
      "kind": "image",
      "uncompressed_bytes": 0,
      "url": "/api/attachments/507f1f77bcf86cd799439012",
      "width": 0
    }
  ],
  "author_id": "6aaff2e8359000011551b35f",
  "channel_id": "6aaff2e8359000011551b360",
  "context_of": null,
  "created_at": "2026-09-28T09:00:00Z",
  "depth": 0,
  "from_agent": true,
  "parent": null,
  "reactions": [
    {
      "emoji": "string",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "root_id": null,
  "updated_at": "2026-09-28T09:00:00Z",
  "workspace_id": "6aaff2e8359000011551b35e",
  "permalink": "https://app.example.com/c/507f1f77bcf86cd799439011/m/507f1f77bcf86cd799439012"
}
PUT/api/channels/{channel_id}/quarantine

Quarantine a channel for a while

A moderator's act: a mod or admin of this channel, or of the workspace. While the quarantine is in force, only moderators may write in it - every other member keeps reading. It lasts minutes, at most a year. DELETE the same path to open it again.

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360/quarantine \
  -X PUT \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"minutes":120}'
{
  "minutes": 120
}
{
  "_id": "6aaff2e8359000011551b35f",
  "created_at": "2026-09-28T09:00:00Z",
  "created_by": "string",
  "deleted_at": "2026-09-28T09:00:00Z",
  "dm_key": "string",
  "key": "GEN-1",
  "kind": "channel",
  "members": [
    {
      "role": "admin",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "name": "incidents",
  "quarantined_until": "2026-09-28T09:00:00Z",
  "task_seq": 0,
  "visibility": "public",
  "workspace_id": "6aaff2e8359000011551b35e"
}
DELETE/api/channels/{channel_id}/quarantine

Lift a channel's quarantine

A moderator's act, by the same rule as placing one. Everybody in the channel may write again immediately.

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360/quarantine \
  -X DELETE \
  -H "Authorization: Bearer $GUILD_KEY"
{
  "_id": "6aaff2e8359000011551b35f",
  "created_at": "2026-09-28T09:00:00Z",
  "created_by": "string",
  "deleted_at": "2026-09-28T09:00:00Z",
  "dm_key": "string",
  "key": "GEN-1",
  "kind": "channel",
  "members": [
    {
      "role": "admin",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "name": "incidents",
  "quarantined_until": "2026-09-28T09:00:00Z",
  "task_seq": 0,
  "visibility": "public",
  "workspace_id": "6aaff2e8359000011551b35e"
}
POST/api/channels/{channel_id}/tasks

Group existing messages into a new task

The raw messages are linked through source_messages; they are never deleted or modified, so the conversation stays readable. Only top level plain messages of this channel can be grouped, each at most once, and the task may name attachments - ids already uploaded into this channel - which come back on every listing exactly as a message's do.

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360/tasks \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"attachments":["507f1f77bcf86cd799439012"],"description":"before the standup","message_ids":["string"],"tags":["string"],"title":"Ship it"}'
{
  "attachments": [
    "507f1f77bcf86cd799439012"
  ],
  "description": "before the standup",
  "message_ids": [
    "string"
  ],
  "tags": [
    "string"
  ],
  "title": "Ship it"
}
{
  "text": "Deployed 4f21c0a - the queue is draining again.",
  "_id": "6aaff2e8359000011551b35f",
  "attachments": [
    {
      "_id": "6aaff2e8359000011551b35f",
      "bytes": 0,
      "content_type": "string",
      "created_at": "2026-09-28T09:00:00Z",
      "filename": "string",
      "height": 0,
      "kind": "image",
      "uncompressed_bytes": 0,
      "url": "/api/attachments/507f1f77bcf86cd799439012",
      "width": 0
    }
  ],
  "author_id": "6aaff2e8359000011551b35f",
  "channel_id": "6aaff2e8359000011551b360",
  "context_of": null,
  "created_at": "2026-09-28T09:00:00Z",
  "depth": 0,
  "from_agent": true,
  "parent": null,
  "reactions": [
    {
      "emoji": "string",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "root_id": null,
  "updated_at": "2026-09-28T09:00:00Z",
  "workspace_id": "6aaff2e8359000011551b35e",
  "permalink": "https://app.example.com/c/507f1f77bcf86cd799439011/m/507f1f77bcf86cd799439012"
}
POST/api/channels/{channel_id}/tasks/prefill

Draft a task from selected messages

Asks the channel's agent for a title and a description for a task being created from the given messages. The agent reads the messages being grouped, the channel's recent traffic, the related messages it may see and the open tasks - and, when the workspace allows it, uses its own tools to look things up.

curl -s https://api.useguild.dev/api/channels/6aaff2e8359000011551b360/tasks/prefill \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message_ids":["507f1f77bcf86cd799439011"]}'
{
  "message_ids": [
    "507f1f77bcf86cd799439011"
  ]
}
{
  "description": "Anyone can reproduce it by deploying while the queue is busy.",
  "title": "The queue backs up after a deploy"
}

Messages and tasks#

17 calls

Reading and writing what people say - and the tasks a message can become.

GET/api/messages/threads

Read several messages' threads at once

The replies under each named message, nested to the same three levels as GET /api/messages/{message_id}/thread. The order the ids were named is kept, and a message the caller may not read (or that does not exist) is left out rather than refused. At most 200 ids.

curl -s https://api.useguild.dev/api/messages/threads?message_id=['6aaff2e8359000011551b361'] \
  -H "Authorization: Bearer $GUILD_KEY"
[
  {
    "insights": {
      "estimate": {
        "p50": "2026-09-28T09:00:00Z",
        "p75": "2026-09-28T09:00:00Z",
        "p90": "2026-09-28T09:00:00Z",
        "p99": "2026-09-28T09:00:00Z",
        "remaining_days": 0
      },
      "generated_at": "2026-09-28T09:00:00Z",
      "milestones": [
        {
          "action": "string",
          "at": "2026-09-28T09:00:00Z",
          "kind": "pull_request_linked",
          "number": 0,
          "repo": "string",
          "sha": "string",
          "summary": "string",
          "url": "https://example.com/acme/pull/12"
        }
      ],
      "milestones_omitted": 0,
      "time_in_status": [
        {
          "millis": 0,
          "status": "backlog"
        }
      ],
      "work": {
        "commits": 0,
        "messages": 0,
        "pull_requests": 0,
        "reactions": 0
      }
    },
    "replies": [
      {
        "_id": "6aaff2e8359000011551b35f",
        "attachments": [
          {
            "_id": "6aaff2e8359000011551b35f",
            "bytes": 0,
            "content_type": "string",
            "created_at": "2026-09-28T09:00:00Z",
            "filename": "string",
            "height": 0,
            "kind": "image",
            "uncompressed_bytes": 0,
            "url": "/api/attachments/507f1f77bcf86cd799439012",
            "width": 0
          }
        ],
        "author_id": "6aaff2e8359000011551b35f",
        "channel_id": "6aaff2e8359000011551b360",
        "context_of": null,
        "created_at": "2026-09-28T09:00:00Z",
        "depth": 0,
        "from_agent": true,
        "parent": null,
        "reactions": [
          {
            "emoji": "string",
            "user_id": "6aaff2e8359000011551b362"
          }
        ],
        "root_id": null,
        "updated_at": "2026-09-28T09:00:00Z",
        "workspace_id": "6aaff2e8359000011551b35e"
      }
    ],
    "text": "Deployed 4f21c0a - the queue is draining again.",
    "type": "message"
  }
]
GET/api/messages/{message_id}

One message: a plain message or a task - and, for a task, its per-task facts when they are asked for.

curl -s https://api.useguild.dev/api/messages/6aaff2e8359000011551b361 \
  -H "Authorization: Bearer $GUILD_KEY"
{
  "_id": "6aaff2e8359000011551b35f",
  "attachments": [
    {
      "_id": "6aaff2e8359000011551b35f",
      "bytes": 0,
      "content_type": "string",
      "created_at": "2026-09-28T09:00:00Z",
      "filename": "string",
      "height": 0,
      "kind": "image",
      "uncompressed_bytes": 0,
      "url": "/api/attachments/507f1f77bcf86cd799439012",
      "width": 0
    }
  ],
  "author_id": "6aaff2e8359000011551b35f",
  "channel_id": "6aaff2e8359000011551b360",
  "context_of": null,
  "created_at": "2026-09-28T09:00:00Z",
  "depth": 0,
  "from_agent": true,
  "parent": null,
  "reactions": [
    {
      "emoji": "string",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "root_id": null,
  "updated_at": "2026-09-28T09:00:00Z",
  "workspace_id": "6aaff2e8359000011551b35e",
  "permalink": "https://app.example.com/c/507f1f77bcf86cd799439011/m/507f1f77bcf86cd799439012",
  "text": "Deployed 4f21c0a - the queue is draining again.",
  "type": "message",
  "insights": {
    "estimate": {
      "p50": "2026-09-28T09:00:00Z",
      "p75": "2026-09-28T09:00:00Z",
      "p90": "2026-09-28T09:00:00Z",
      "p99": "2026-09-28T09:00:00Z",
      "remaining_days": 0
    },
    "generated_at": "2026-09-28T09:00:00Z",
    "milestones": [
      {
        "action": "string",
        "at": "2026-09-28T09:00:00Z",
        "kind": "pull_request_linked",
        "number": 0,
        "repo": "string",
        "sha": "string",
        "summary": "string",
        "url": "https://example.com/acme/pull/12"
      }
    ],
    "milestones_omitted": 0,
    "time_in_status": [
      {
        "millis": 0,
        "status": "backlog"
      }
    ],
    "work": {
      "commits": 0,
      "messages": 0,
      "pull_requests": 0,
      "reactions": 0
    }
  }
}
PATCH/api/messages/{message_id}

Edit a task's title and description

Rewrites what a task says, which is the one thing no other write path reaches: a status and an assignee are decisions about the work, and this is the work's own words.

curl -s https://api.useguild.dev/api/messages/6aaff2e8359000011551b361 \
  -X PATCH \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"description":"It fails about one run in twenty.","title":"Fix the flaky sync test"}'
{
  "description": "It fails about one run in twenty.",
  "title": "Fix the flaky sync test"
}
{
  "text": "Deployed 4f21c0a - the queue is draining again.",
  "_id": "6aaff2e8359000011551b35f",
  "attachments": [
    {
      "_id": "6aaff2e8359000011551b35f",
      "bytes": 0,
      "content_type": "string",
      "created_at": "2026-09-28T09:00:00Z",
      "filename": "string",
      "height": 0,
      "kind": "image",
      "uncompressed_bytes": 0,
      "url": "/api/attachments/507f1f77bcf86cd799439012",
      "width": 0
    }
  ],
  "author_id": "6aaff2e8359000011551b35f",
  "channel_id": "6aaff2e8359000011551b360",
  "context_of": null,
  "created_at": "2026-09-28T09:00:00Z",
  "depth": 0,
  "from_agent": true,
  "parent": null,
  "reactions": [
    {
      "emoji": "string",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "root_id": null,
  "updated_at": "2026-09-28T09:00:00Z",
  "workspace_id": "6aaff2e8359000011551b35e",
  "permalink": "https://app.example.com/c/507f1f77bcf86cd799439011/m/507f1f77bcf86cd799439012"
}
DELETE/api/messages/{message_id}

Delete a message or a task

Removes the message from the database: nothing is kept, nothing is hidden, and there is no way back - the counterpart of a task's status, which is how work is called off without losing what was said.

curl -s https://api.useguild.dev/api/messages/6aaff2e8359000011551b361 \
  -X DELETE \
  -H "Authorization: Bearer $GUILD_KEY"
POST/api/messages/{message_id}/assignee

Hand a task to somebody

A task always has somebody: it is owned by whoever created it from the moment it exists, so this moves it from one member of the channel to another and never leaves it unowned. Any member of the task's channel may do it - handing it to yourself is the same call, and is what taking a task on looks like.

curl -s https://api.useguild.dev/api/messages/6aaff2e8359000011551b361/assignee \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"assignee":"string"}'
{
  "assignee": "string"
}
{
  "text": "Deployed 4f21c0a - the queue is draining again.",
  "_id": "6aaff2e8359000011551b35f",
  "attachments": [
    {
      "_id": "6aaff2e8359000011551b35f",
      "bytes": 0,
      "content_type": "string",
      "created_at": "2026-09-28T09:00:00Z",
      "filename": "string",
      "height": 0,
      "kind": "image",
      "uncompressed_bytes": 0,
      "url": "/api/attachments/507f1f77bcf86cd799439012",
      "width": 0
    }
  ],
  "author_id": "6aaff2e8359000011551b35f",
  "channel_id": "6aaff2e8359000011551b360",
  "context_of": null,
  "created_at": "2026-09-28T09:00:00Z",
  "depth": 0,
  "from_agent": true,
  "parent": null,
  "reactions": [
    {
      "emoji": "string",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "root_id": null,
  "updated_at": "2026-09-28T09:00:00Z",
  "workspace_id": "6aaff2e8359000011551b35e",
  "permalink": "https://app.example.com/c/507f1f77bcf86cd799439011/m/507f1f77bcf86cd799439012"
}
GET/api/messages/{message_id}/context

The context of a task

The messages grouped into this task, in the order they were given when it was grouped. They are not replies and not a thread: they are the material the task was made from, hidden from the channel listing by default and never deleted. Each one carries context_of set to the task id.

curl -s https://api.useguild.dev/api/messages/6aaff2e8359000011551b361/context \
  -H "Authorization: Bearer $GUILD_KEY"
{
  "context": [
    {
      "_id": "6aaff2e8359000011551b35f",
      "attachments": [
        {
          "_id": "6aaff2e8359000011551b35f",
          "bytes": 0,
          "content_type": "string",
          "created_at": "2026-09-28T09:00:00Z",
          "filename": "string",
          "height": 0,
          "kind": "image",
          "uncompressed_bytes": 0,
          "url": "/api/attachments/507f1f77bcf86cd799439012",
          "width": 0
        }
      ],
      "author_id": "6aaff2e8359000011551b35f",
      "channel_id": "6aaff2e8359000011551b360",
      "context_of": null,
      "created_at": "2026-09-28T09:00:00Z",
      "depth": 0,
      "from_agent": true,
      "parent": null,
      "reactions": [
        {
          "emoji": "string",
          "user_id": "6aaff2e8359000011551b362"
        }
      ],
      "root_id": null,
      "updated_at": "2026-09-28T09:00:00Z",
      "workspace_id": "6aaff2e8359000011551b35e",
      "permalink": "https://app.example.com/c/507f1f77bcf86cd799439011/m/507f1f77bcf86cd799439012",
      "text": "Deployed 4f21c0a - the queue is draining again.",
      "type": "message"
    }
  ],
  "task": {
    "_id": "6aaff2e8359000011551b35f",
    "attachments": [
      {
        "_id": "6aaff2e8359000011551b35f",
        "bytes": 0,
        "content_type": "string",
        "created_at": "2026-09-28T09:00:00Z",
        "filename": "string",
        "height": 0,
        "kind": "image",
        "uncompressed_bytes": 0,
        "url": "/api/attachments/507f1f77bcf86cd799439012",
        "width": 0
      }
    ],
    "author_id": "6aaff2e8359000011551b35f",
    "channel_id": "6aaff2e8359000011551b360",
    "context_of": null,
    "created_at": "2026-09-28T09:00:00Z",
    "depth": 0,
    "from_agent": true,
    "parent": null,
    "reactions": [
      {
        "emoji": "string",
        "user_id": "6aaff2e8359000011551b362"
      }
    ],
    "root_id": null,
    "updated_at": "2026-09-28T09:00:00Z",
    "workspace_id": "6aaff2e8359000011551b35e",
    "text": "Deployed 4f21c0a - the queue is draining again.",
    "type": "message",
    "permalink": "https://app.example.com/c/507f1f77bcf86cd799439011/m/507f1f77bcf86cd799439012"
  }
}
POST/api/messages/{message_id}/dependencies

Make a task depend on another task

Records a dependency: this task requires, blocks or duplicates the task named by target (its id or its public key). A dependency is a decision somebody makes, so any member of the task's channel may make it, to themselves or on somebody else's behalf - the rule a status and an assignee already follow.

curl -s https://api.useguild.dev/api/messages/6aaff2e8359000011551b361/dependencies \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind":"requires","target":"TK-101"}'
{
  "kind": "requires",
  "target": "TK-101"
}
{
  "text": "Deployed 4f21c0a - the queue is draining again.",
  "_id": "6aaff2e8359000011551b35f",
  "attachments": [
    {
      "_id": "6aaff2e8359000011551b35f",
      "bytes": 0,
      "content_type": "string",
      "created_at": "2026-09-28T09:00:00Z",
      "filename": "string",
      "height": 0,
      "kind": "image",
      "uncompressed_bytes": 0,
      "url": "/api/attachments/507f1f77bcf86cd799439012",
      "width": 0
    }
  ],
  "author_id": "6aaff2e8359000011551b35f",
  "channel_id": "6aaff2e8359000011551b360",
  "context_of": null,
  "created_at": "2026-09-28T09:00:00Z",
  "depth": 0,
  "from_agent": true,
  "parent": null,
  "reactions": [
    {
      "emoji": "string",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "root_id": null,
  "updated_at": "2026-09-28T09:00:00Z",
  "workspace_id": "6aaff2e8359000011551b35e",
  "permalink": "https://app.example.com/c/507f1f77bcf86cd799439011/m/507f1f77bcf86cd799439012"
}
DELETE/api/messages/{message_id}/dependencies/{target}

Remove a task's dependency on another task

Removes the dependency on the task named by target (its id or its public key), whoever added it. Any member of the task's channel may do it. Removing a dependency that is not there changes nothing and announces nothing, so a retry is not an error; the task's references are untouched.

curl -s https://api.useguild.dev/api/messages/6aaff2e8359000011551b361/dependencies/string \
  -X DELETE \
  -H "Authorization: Bearer $GUILD_KEY"
{
  "text": "Deployed 4f21c0a - the queue is draining again.",
  "_id": "6aaff2e8359000011551b35f",
  "attachments": [
    {
      "_id": "6aaff2e8359000011551b35f",
      "bytes": 0,
      "content_type": "string",
      "created_at": "2026-09-28T09:00:00Z",
      "filename": "string",
      "height": 0,
      "kind": "image",
      "uncompressed_bytes": 0,
      "url": "/api/attachments/507f1f77bcf86cd799439012",
      "width": 0
    }
  ],
  "author_id": "6aaff2e8359000011551b35f",
  "channel_id": "6aaff2e8359000011551b360",
  "context_of": null,
  "created_at": "2026-09-28T09:00:00Z",
  "depth": 0,
  "from_agent": true,
  "parent": null,
  "reactions": [
    {
      "emoji": "string",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "root_id": null,
  "updated_at": "2026-09-28T09:00:00Z",
  "workspace_id": "6aaff2e8359000011551b35e",
  "permalink": "https://app.example.com/c/507f1f77bcf86cd799439011/m/507f1f77bcf86cd799439012"
}
GET/api/messages/{message_id}/plugins

The plugins an agent's answer used

Which plugin instances an agent called while producing an answer - the disclosure a reader may open on an agent's message. Empty for a message a person wrote, and for an agent that called no tool. 403 for an external member, 404 when there is no such message.

curl -s https://api.useguild.dev/api/messages/6aaff2e8359000011551b361/plugins \
  -H "Authorization: Bearer $GUILD_KEY"
{
  "message_id": "6aaff2e8359000011551b361",
  "plugins": [
    "string"
  ]
}
POST/api/messages/{message_id}/reactions

Add an emoji reaction to a message

Idempotent: the same person reacting with the same emoji twice is one reaction. The updated message is returned with its reactions.

curl -s https://api.useguild.dev/api/messages/6aaff2e8359000011551b361/reactions \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"emoji":"\ud83d\udc4d"}'
{
  "emoji": "\ud83d\udc4d"
}
{
  "text": "Deployed 4f21c0a - the queue is draining again.",
  "_id": "6aaff2e8359000011551b35f",
  "attachments": [
    {
      "_id": "6aaff2e8359000011551b35f",
      "bytes": 0,
      "content_type": "string",
      "created_at": "2026-09-28T09:00:00Z",
      "filename": "string",
      "height": 0,
      "kind": "image",
      "uncompressed_bytes": 0,
      "url": "/api/attachments/507f1f77bcf86cd799439012",
      "width": 0
    }
  ],
  "author_id": "6aaff2e8359000011551b35f",
  "channel_id": "6aaff2e8359000011551b360",
  "context_of": null,
  "created_at": "2026-09-28T09:00:00Z",
  "depth": 0,
  "from_agent": true,
  "parent": null,
  "reactions": [
    {
      "emoji": "string",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "root_id": null,
  "updated_at": "2026-09-28T09:00:00Z",
  "workspace_id": "6aaff2e8359000011551b35e",
  "permalink": "https://app.example.com/c/507f1f77bcf86cd799439011/m/507f1f77bcf86cd799439012"
}
DELETE/api/messages/{message_id}/reactions/{emoji}

Remove your emoji reaction from a message

The emoji is percent-encoded in the path. Idempotent: removing a reaction that is not there changes nothing. Only the caller's own reaction goes.

curl -s https://api.useguild.dev/api/messages/6aaff2e8359000011551b361/reactions/string \
  -X DELETE \
  -H "Authorization: Bearer $GUILD_KEY"
{
  "text": "Deployed 4f21c0a - the queue is draining again.",
  "_id": "6aaff2e8359000011551b35f",
  "attachments": [
    {
      "_id": "6aaff2e8359000011551b35f",
      "bytes": 0,
      "content_type": "string",
      "created_at": "2026-09-28T09:00:00Z",
      "filename": "string",
      "height": 0,
      "kind": "image",
      "uncompressed_bytes": 0,
      "url": "/api/attachments/507f1f77bcf86cd799439012",
      "width": 0
    }
  ],
  "author_id": "6aaff2e8359000011551b35f",
  "channel_id": "6aaff2e8359000011551b360",
  "context_of": null,
  "created_at": "2026-09-28T09:00:00Z",
  "depth": 0,
  "from_agent": true,
  "parent": null,
  "reactions": [
    {
      "emoji": "string",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "root_id": null,
  "updated_at": "2026-09-28T09:00:00Z",
  "workspace_id": "6aaff2e8359000011551b35e",
  "permalink": "https://app.example.com/c/507f1f77bcf86cd799439011/m/507f1f77bcf86cd799439012"
}
DELETE/api/messages/{message_id}/references/{target}

Remove a task's reference to another task

Removes the reference to the task named by target (its id or its public key) - a mention somebody decided is not a link. Any member of the task's channel may do it, and removing one that is not there changes nothing.

curl -s https://api.useguild.dev/api/messages/6aaff2e8359000011551b361/references/string \
  -X DELETE \
  -H "Authorization: Bearer $GUILD_KEY"
{
  "text": "Deployed 4f21c0a - the queue is draining again.",
  "_id": "6aaff2e8359000011551b35f",
  "attachments": [
    {
      "_id": "6aaff2e8359000011551b35f",
      "bytes": 0,
      "content_type": "string",
      "created_at": "2026-09-28T09:00:00Z",
      "filename": "string",
      "height": 0,
      "kind": "image",
      "uncompressed_bytes": 0,
      "url": "/api/attachments/507f1f77bcf86cd799439012",
      "width": 0
    }
  ],
  "author_id": "6aaff2e8359000011551b35f",
  "channel_id": "6aaff2e8359000011551b360",
  "context_of": null,
  "created_at": "2026-09-28T09:00:00Z",
  "depth": 0,
  "from_agent": true,
  "parent": null,
  "reactions": [
    {
      "emoji": "string",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "root_id": null,
  "updated_at": "2026-09-28T09:00:00Z",
  "workspace_id": "6aaff2e8359000011551b35e",
  "permalink": "https://app.example.com/c/507f1f77bcf86cd799439011/m/507f1f77bcf86cd799439012"
}
POST/api/messages/{message_id}/replies

Reply to a message. Replies nest at most three levels below the root.

curl -s https://api.useguild.dev/api/messages/6aaff2e8359000011551b361/replies \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text":"agreed"}'
{
  "text": "agreed"
}
{
  "text": "Deployed 4f21c0a - the queue is draining again.",
  "_id": "6aaff2e8359000011551b35f",
  "attachments": [
    {
      "_id": "6aaff2e8359000011551b35f",
      "bytes": 0,
      "content_type": "string",
      "created_at": "2026-09-28T09:00:00Z",
      "filename": "string",
      "height": 0,
      "kind": "image",
      "uncompressed_bytes": 0,
      "url": "/api/attachments/507f1f77bcf86cd799439012",
      "width": 0
    }
  ],
  "author_id": "6aaff2e8359000011551b35f",
  "channel_id": "6aaff2e8359000011551b360",
  "context_of": null,
  "created_at": "2026-09-28T09:00:00Z",
  "depth": 0,
  "from_agent": true,
  "parent": null,
  "reactions": [
    {
      "emoji": "string",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "root_id": null,
  "updated_at": "2026-09-28T09:00:00Z",
  "workspace_id": "6aaff2e8359000011551b35e",
  "permalink": "https://app.example.com/c/507f1f77bcf86cd799439011/m/507f1f77bcf86cd799439012"
}
POST/api/messages/{message_id}/status

Move a task to another status

A task's status is a fixed set of five values: backlog (agreed, and deliberately not being worked on yet), todo (where every task starts), in_progress, done and discarded (dropped rather than finished). Any status may follow any other - a task closed by mistake has to be reopenable - so there are no transition rules.

curl -s https://api.useguild.dev/api/messages/6aaff2e8359000011551b361/status \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status":"backlog"}'
{
  "status": "backlog"
}
{
  "text": "Deployed 4f21c0a - the queue is draining again.",
  "_id": "6aaff2e8359000011551b35f",
  "attachments": [
    {
      "_id": "6aaff2e8359000011551b35f",
      "bytes": 0,
      "content_type": "string",
      "created_at": "2026-09-28T09:00:00Z",
      "filename": "string",
      "height": 0,
      "kind": "image",
      "uncompressed_bytes": 0,
      "url": "/api/attachments/507f1f77bcf86cd799439012",
      "width": 0
    }
  ],
  "author_id": "6aaff2e8359000011551b35f",
  "channel_id": "6aaff2e8359000011551b360",
  "context_of": null,
  "created_at": "2026-09-28T09:00:00Z",
  "depth": 0,
  "from_agent": true,
  "parent": null,
  "reactions": [
    {
      "emoji": "string",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "root_id": null,
  "updated_at": "2026-09-28T09:00:00Z",
  "workspace_id": "6aaff2e8359000011551b35e",
  "permalink": "https://app.example.com/c/507f1f77bcf86cd799439011/m/507f1f77bcf86cd799439012"
}
POST/api/messages/{message_id}/summary

Summarise a task, and judge its references

Reads the task, the messages it was grouped from, its whole thread (with the pull requests, commits, CI results and comments in it), the pull-request comments and failed CI logs fetched from GitHub, and the tasks it depends on (each with its own status and activity) - but never a diff. It asks two services at once.

curl -s https://api.useguild.dev/api/messages/6aaff2e8359000011551b361/summary \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY"
{
  "comments": 0,
  "dependencies": 0,
  "events": 0,
  "logs": 0,
  "messages": 0,
  "model": "string",
  "provider": "openai",
  "referenced": 0,
  "standup": {
    "blockers": "string",
    "done": "string",
    "left": "string"
  },
  "suggestion_model": "string",
  "suggestion_threshold": 0,
  "suggestions": [
    {
      "confidence": 0,
      "key": "GEN-1",
      "kind": null,
      "reason": "The message names somebody outside the channel.",
      "suggested": true,
      "task_id": "6aaff2e8359000011551b363"
    }
  ],
  "summary": "string",
  "truncated": true
}
POST/api/messages/{message_id}/tags

Set a task's tags

The tags a task carries, as the whole list: one call both adds and removes, and {"tags": []} clears every tag - so a client never has to say which of the two it meant, and re-sending what it just read changes nothing. Any member of the task's channel may do it, the rule the status and the assignee follow.

curl -s https://api.useguild.dev/api/messages/6aaff2e8359000011551b361/tags \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tags":["bug","ops"]}'
{
  "tags": [
    "bug",
    "ops"
  ]
}
{
  "text": "Deployed 4f21c0a - the queue is draining again.",
  "_id": "6aaff2e8359000011551b35f",
  "attachments": [
    {
      "_id": "6aaff2e8359000011551b35f",
      "bytes": 0,
      "content_type": "string",
      "created_at": "2026-09-28T09:00:00Z",
      "filename": "string",
      "height": 0,
      "kind": "image",
      "uncompressed_bytes": 0,
      "url": "/api/attachments/507f1f77bcf86cd799439012",
      "width": 0
    }
  ],
  "author_id": "6aaff2e8359000011551b35f",
  "channel_id": "6aaff2e8359000011551b360",
  "context_of": null,
  "created_at": "2026-09-28T09:00:00Z",
  "depth": 0,
  "from_agent": true,
  "parent": null,
  "reactions": [
    {
      "emoji": "string",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "root_id": null,
  "updated_at": "2026-09-28T09:00:00Z",
  "workspace_id": "6aaff2e8359000011551b35e",
  "permalink": "https://app.example.com/c/507f1f77bcf86cd799439011/m/507f1f77bcf86cd799439012"
}
GET/api/messages/{message_id}/thread

The whole thread under a message, nested.

curl -s https://api.useguild.dev/api/messages/6aaff2e8359000011551b361/thread \
  -H "Authorization: Bearer $GUILD_KEY"
{
  "_id": "6aaff2e8359000011551b35f",
  "attachments": [
    {
      "_id": "6aaff2e8359000011551b35f",
      "bytes": 0,
      "content_type": "string",
      "created_at": "2026-09-28T09:00:00Z",
      "filename": "string",
      "height": 0,
      "kind": "image",
      "uncompressed_bytes": 0,
      "url": "/api/attachments/507f1f77bcf86cd799439012",
      "width": 0
    }
  ],
  "author_id": "6aaff2e8359000011551b35f",
  "channel_id": "6aaff2e8359000011551b360",
  "context_of": null,
  "created_at": "2026-09-28T09:00:00Z",
  "depth": 0,
  "from_agent": true,
  "parent": null,
  "reactions": [
    {
      "emoji": "string",
      "user_id": "6aaff2e8359000011551b362"
    }
  ],
  "root_id": null,
  "updated_at": "2026-09-28T09:00:00Z",
  "workspace_id": "6aaff2e8359000011551b35e",
  "permalink": "https://app.example.com/c/507f1f77bcf86cd799439011/m/507f1f77bcf86cd799439012",
  "text": "Deployed 4f21c0a - the queue is draining again.",
  "type": "message",
  "insights": {
    "estimate": {
      "p50": "2026-09-28T09:00:00Z",
      "p75": "2026-09-28T09:00:00Z",
      "p90": "2026-09-28T09:00:00Z",
      "p99": "2026-09-28T09:00:00Z",
      "remaining_days": 0
    },
    "generated_at": "2026-09-28T09:00:00Z",
    "milestones": [
      {
        "action": "string",
        "at": "2026-09-28T09:00:00Z",
        "kind": "pull_request_linked",
        "number": 0,
        "repo": "string",
        "sha": "string",
        "summary": "string",
        "url": "https://example.com/acme/pull/12"
      }
    ],
    "milestones_omitted": 0,
    "time_in_status": [
      {
        "millis": 0,
        "status": "backlog"
      }
    ],
    "work": {
      "commits": 0,
      "messages": 0,
      "pull_requests": 0,
      "reactions": 0
    }
  },
  "replies": [
    {
      "text": "Deployed 4f21c0a - the queue is draining again."
    }
  ]
}
1 call

One query across messages, tasks, commits, pull requests, runs and channels.

Activity#

2 calls

What happened while a reader was away, in the threads they are part of.

GET/api/activity

Read your activity

Everything that happened in the threads you are part of, newest first: messages, replies and integration events (a commit, a pull request, a run) in a thread you wrote in or a task you are on, and the changes to those tasks (a status, a hand-over, an edit, a dependency). Your own acts are left out.

curl -s https://api.useguild.dev/api/activity \
  -H "Authorization: Bearer $GUILD_KEY"
{
  "items": [
    {
      "actor_id": null,
      "at": "string",
      "channel_id": "6aaff2e8359000011551b360",
      "data": "string",
      "kind": "message.created",
      "message": {
        "text": "Deployed 4f21c0a - the queue is draining again."
      },
      "target_id": null
    }
  ],
  "next": "string",
  "seen_at": "string"
}
POST/api/activity/seen

Mark your activity as seen

Moves your activity cursor to at, or to now when it is absent, so the feed answers only what happens next. Called when the reader opens the Activity section, with the newest item it showed - so an item that arrived between the read and this call is still shown next time.

curl -s https://api.useguild.dev/api/activity/seen \
  -X POST \
  -H "Authorization: Bearer $GUILD_KEY" \
  -H "Content-Type: application/json" \
  -d '{"at":"string"}'
{
  "at": "string"
}

The console's API is not documented here, because it is not yours to call: it is a different credential, for the people who run the platform, and it only ever answers to theirs.