Denly

Denly documentation

Everything needed to connect work, run coding agents on your machines, and keep control of each gate.

Product overview

Denly coordinates coding work while source repositories stay on runner machines.

Denly is a control plane for Claude Code, Codex, Cursor, Grok Build, and Antigravity. A task follows a visible lifecycle: plan, approval, build, review, and merge. Source repositories are never uploaded to Denly.

Workspace policy, projects, tasks, Chat, and runner health are visible in the dashboard; code execution stays on your machine.

Quickstart

Add a project and bring its runner online.

  1. Sign in and create or join a workspace.
  2. Use App bar project switcher → Add a project. Connect GitHub, GitLab, or a manual repository, or create a local project with no Git.
  3. For a local project, optionally enter its exact runner folder. Leaving it blank uses ~/Denly/<project-id>.
  4. Install the Denly CLI for your platform, then run denly login.
  5. Copy the exact dashboard command, such as denly install my-project, and run it on the machine that will do the work.
Verify setup
denly login
denly install my-project
denly status my-project

Projects & repositories

A Denly project maps work to a runner folder, with Git optional.

Add projects from App bar project switcher → Add a project. A Git project can use GitHub, GitLab, or a manually registered repository. A Local project · no Git starts as a plain folder: the runner never initializes Git, and its task flow ends with Review changes → Mark complete instead of a pull request and Merge.

denly install <project> clones a Git project or creates its no-Git folder at ~/Denly/<project-id>. For a no-Git project, denly install <project> --path <folder> creates that exact folder when missing or explicitly adopts it when it already exists. For a Git project, --path only adopts an existing checkout after validating its origin.

Absolute runner paths stay machine-local; Denly stores the project kind, not one teammate's filesystem location. Project settings control who can see the project and which personal folder it sits in.

Owners and admins can also change a project's source there, under Danger zone: connect a local project to a Git repository, or detach the repository and keep working in the folder. Connecting asks for the host, the repository path, and its default branch – name the branch the repository actually uses, because it becomes the base for every diff, pull request and deploy. For a self-managed GitLab, give the instance address too; blank means gitlab.com. Detaching leaves the checkout and its .git in place on every machine. Stop any live runner or Chat session first – the server refuses while one holds the project – then re-run denly install on each machine so it picks up the new kind.

The same panel moves a project to another workspace you own. Runner credentials, the GitHub App installation and integration connections stay behind, because each is trusted per workspace; every machine re-runs denly install afterwards.

You set the naming conventions on the Defaults screen, at workspace scope or per project: the branch, commit and pull-request title shapes the agent uses, with placeholders such as the task number and slug. The merge method (squash, merge or rebase) lives there too.

Project organization & multi-repo work

Organize personal views and run work across repositories.

  • Personal folders organize your project switcher without changing teammates’ layouts.
  • Group runners share one machine credential and parent folder across several projects.
  • Tasks and Chat can use cross-project Skills and Docs when their execution scope includes those projects.
  • Choose a runner at task creation or change its execution scope before work starts.
Group runner
denly groups
denly install --group platform
denly status --all
denly restart --all

Tasks & backlog

Create, route, revise, and complete app-native or connected work.

Create tasks from a project or the all-project view. Set an assignee, a runtime and model, attachments, whether the task waits in the backlog or goes straight to the planner, whether it runs on the project or group runner, and any task it should wait for.

At the approval gate you approve the plan or send it back with notes, and the agent plans again. The brief itself – title and description – is editable while the task is in the backlog; after that, notes at the gate are how you change the ask. A backlog task can also skip planning and go straight to build.

During execution, answer the agent's questions, retry a failure once you have fixed its cause (from the last step, from the beginning, or fresh on another runtime), review the pull request, and merge through the recorded gate.

Other controls on a task: park it and resume it later, set its priority, make it private, and change which runner it uses – that last one restarts the task, so it asks first. The author can delete a task while it sits in the backlog; owners and admins can delete one at any stage.

Live Chat

Open a leased coding session from the dashboard.

Live Chat keeps a runtime attached to one project while you exchange messages and watch the tools it runs. Start it from the Chat screen: the project is whichever one the app bar has in scope, and the dialog picks the runtime, the model, whether the session is private, and – for a project in a group – whether to use the group runner. End the session normally to release its lease, or force end one that has wedged.

Scoped Skills & Docs

Control reusable instructions and repository context.

Skills are executable operating instructions; Docs are reference context. Both come in four layers: shared across the workspace, per project, yours alone, and yours for one project. Preview context shows exactly what the runner will receive, assembled by the same matcher the runner uses.

Install a skill straight from GitHub: search the index, preview the file, then install it into the layer you choose. Cross-project context follows the runner – a task or chat on a group runner can see the group's projects, and a project runner sees only its own.

Skill location
skills/database-migrations/SKILL.md

AI runtimes & models

Set workspace defaults and project overrides.

Supported runtimes are Claude Code, OpenAI Codex, Cursor, Grok Build, and Antigravity. Availability is machine-specific: each CLI must be installed and authenticated where the runner executes. Denly stores selection policy, not your vendor subscription.

Machines & runners

Enroll machines, inspect health, and control supervisors.

Machines shows liveness, the runner build, how the agent signs in, claim scope, command consent, and sanitized diagnostics. Each project runner has a scoped credential; revoking it stops future authenticated calls but does not delete the checkout or touch the machine.

Enrolling a machine needs your approval once: denly login opens a code in the browser, you confirm it, and that installation is trusted from then on. Claim scope decides what the runner picks up – only tasks assigned to you, or every actionable task in the project.

Runner operations
denly projects
denly doctor
denly logs my-project
denly restart my-project
denly upgrade

Teams, privacy & assignments

Share policy without sharing local credentials.

Owners manage billing and workspace lifecycle. Owners and admins manage members, projects, integrations, schedules, and shared configuration. Members use granted projects and can view integration health. Private projects limit discovery and access; assignments route work to the intended teammate’s runner.

Invite teammates by email from the workspace screen; an invite names the role it grants and can be withdrawn before it is accepted. A member who finds a project they cannot open can request access, and an owner or admin grants it. The workspace audit log records the actions that change policy – who invited whom, who changed a role, who connected or deleted an integration, and who resolved a plan freeze.

Flows, reviews, gates & schedules

Make lifecycle policy explicit.

Flows combine planning, build, AI or security review, human sign-off, command steps, QA, deploy, and merge. Workspace policy may be optional, a required minimum, or exact. Schedules create ordinary tasks on daily, weekly, or monthly cadences and skip an occurrence when the previous task remains open.

Integrations

Connect trackers and team chat without exposing secrets.

All members can inspect redacted connection health and recent queued, processed, or failed deliveries. Only owners and admins can create, edit, enable, disable, or delete connections.

Tracker connections support tag, assignee, and create triggers plus comments, worklogs, and status mappings. Chat connections accept slash commands and supported reactions, map channels to projects, and deliver notifications.

Loading the provider catalog…

Secrets are write-only.The inbound secret appears once after creation. Stored credentials are sealed and all later reads are redacted. Use Recent events for webhook authentication, normalization, queue, and outbound diagnostics.

How to connect any provider

These runbooks are for workspace owners and admins. A connection belongs to the workspace and is scoped to exactly one project, which every task it creates lands in. Chat connections can still send individual channels elsewhere with a channel-to-project mapping, and that mapping wins; the project scope is the floor under it, not a ceiling. A chat message that names a project explicitly beats both. For task-producing providers, leave routing blank for unassigned tasks, choose a default member, or map external users to Denly members.

Reading Recent events

Every runbook below ends by checking Workspace → Integrations → your connection → Recent events. Each delivery that gets that far shows as one of:

  • Queued or Processing – it arrived and is being worked through. Both are momentary.
  • Processed – it became a Denly task, or updated one.
  • Skipped – expected, and usually not a fault. The Diagnostic column says which rule declined it: the tag or label did not match, the assignee or project did not match, or the item was already linked to a task another delivery created first.
  • Failed – Denly reached the provider and the provider refused. The Diagnostic column carries its error verbatim.

An empty list means something different. Denly refuses a delivery with a bad secret before the queue, so it never becomes a row, and a redelivery Denly has already handled leaves no row either. A connection still reading Awaiting first event after you have tried it is therefore rejecting deliveries rather than skipping them. Check the secret on both sides, then read the provider's own webhook delivery log, the only place those attempts appear.

Trackers & project management

Jira

Import Jira issues and tagged comments, then synchronize comments, transitions, worklogs, and assignment routing.

1. Create a Jira API token

In Atlassian:

  1. Open Atlassian account security.
  2. Select Create API token.
  3. Name it Denly, create it, and copy it immediately. Jira shows it only once.

Use the token with the email address of the Jira account. Do not paste the token into a webhook payload.

2. Create the Denly connection

Open Workspace → Integrations → Connect → Jira.

  • External workspace or repository: your Jira project key, such as ENG.
  • API base URL: your Jira site, such as https://acme.atlassian.net.
  • Account email and API token: the Jira account's email address and the token from step 1.
  • Trigger tag: denly; keep Trigger on create disabled for the first test.
  • Initial Denly stage: Plan first. Enable Post worklogs to have Denly log time against the issue.

Routing is optional. Leave Default Denly assignee blank for unassigned tasks, or choose one workspace member to own everything from this connection.

For per-person routing use Route by external assignee: each row pairs one person in the provider (left) with a workspace member (right), and + adds another row. Routing follows the item's assignee in the provider, not whoever created it, and a matching row beats the default above.

The left-hand value must be the identifier the provider actually sends, which the field's placeholder names for you. Matching ignores surrounding spaces and letter case, but nothing else: a near-miss routes nothing and looks the same as a broken integration.

Read Jira webhook documentation.

3. Register the Jira webhook

In Jira open Settings → System → Webhooks → Create webhook. Set the generated Denly URL, subscribe to issue-created/updated and issue-comment events, and leave the JQL filter matching the issues you want Denly to see.

Authenticate the webhook. Jira Cloud has no custom-header field, so paste the Denly inbound secret into the webhook’s Secret box. Jira then signs each delivery and sends X-Hub-Signature: sha256=…, which Denly verifies against the same secret. Save the webhook.

Jira Data Center has no Secret box but does allow headers: send the inbound secret as X-Denly-Token instead. Denly accepts either scheme. The same applies to a Jira Automation rule using Send web request, which supports custom headers but does not sign.

A self-managed Jira server must be publicly reachable over HTTPS. Jira’s webhook delivery log is the first place to check.

4. Test and troubleshoot
  1. Create an issue in the connected project and add the denly label.
  2. Open Workspace → Integrations → Jira → Recent events.
  3. Processed means a task was created; Skipped means the label, assignee, project or create rule declined it, and the Diagnostic column says which.

Nothing at all, and the card still on Awaiting first event, means every delivery is being rejected before the queue: re-check the webhook Secret, or the X-Denly-Token header, against the inbound secret. The secret is shown only once, so replace it on both sides rather than guessing. Jira's own webhook delivery log is where those rejected attempts are recorded.

Status mappings move the Jira issue as the task progresses. The setting appears once the connection is saved, not while you are creating it: the dropdown is read live from your project's workflow, which needs the credentials to be stored first. Until you set it, the defaults apply. Reopen the connection and each row offers your project's real statuses – so there is no name to spell. Leave a stage on Default to keep the value shown in the option, or set it to No change to leave the issue where it is. If Denly cannot read your workflow – the stored credentials have lapsed, or Jira is having a bad minute – the rows fall back to a text box plus a No change box, so the mapping stays editable rather than blocking the rest of the form. Jira transitions are edges rather than states, so a status that is not reachable from the issue's current one is skipped rather than forced.

Comments containing #denly on linked issues become revision work.

YouTrack

Import YouTrack issues and tagged comments and synchronize comments, state, work time, and assignment routing.

1. Create a YouTrack token

In YouTrack:

  1. Open your avatar → Profile.
  2. Open Account Security.
  3. Select New token.
  4. Name it Denly.
  5. Select the YouTrack scope.
  6. Create it and copy it immediately.

YouTrack only shows the token once. It is used as a bearer token for API access. Read JetBrains’ permanent token guide.

2. Create the Denly connection

Open Workspace → Integrations → Connect → YouTrack.

  • External workspace or repository: project short name, such as DEV.
  • API base URL: https://your-company.youtrack.cloud or your public HTTPS Server URL.
  • Permanent token: paste the token.
  • State field name: leave blank unless your project renamed its State field or has more than one.
  • Trigger tag: denly; leave Trigger on create disabled initially.
  • Initial Denly stage: Plan first, with Post worklogs enabled.

Routing is optional. Leave Default Denly assignee blank for unassigned tasks, or choose one workspace member to own everything from this connection.

For per-person routing use Route by external assignee: each row pairs one person in the provider (left) with a workspace member (right), and + adds another row. Routing follows the item's assignee in the provider, not whoever created it, and a matching row beats the default above.

The left-hand value must be the identifier the provider actually sends, which the field's placeholder names for you. Matching ignores surrounding spaces and letter case, but nothing else: a near-miss routes nothing and looks the same as a broken integration.

Create the connection and copy the generated webhook URL and inbound secret. The secret is shown only once.

3. Make YouTrack send events

Go to Administration → Workflows, create a JavaScript workflow, add an On-change rule, paste this script, replace DENLY_URL and DENLY_SECRET, then attach and enable the workflow for your project.

const entities = require('@jetbrains/youtrack-scripting-api/entities');
const http = require('@jetbrains/youtrack-scripting-api/http');

const DENLY_URL = 'https://your-public-host/integrations/youtrack/webhook/YOUR_CONNECTION_ID';
const DENLY_SECRET = 'paste-your-inbound-secret-here';

function sendIssue(issue, eventName) {
  const tags = [];
  issue.tags.forEach(tag => tags.push({ name: tag.name }));

  const assignee = issue.fields.Assignee;
  const reporter = issue.reporter;
  const payload = {
    event: eventName,
    issue: {
      idReadable: issue.idReadable,
      id: issue.id,
      summary: issue.summary || '',
      description: issue.description || '',
      tags: tags,
      assignee: assignee ? { login: assignee.login, name: assignee.name } : {},
      reporter: reporter ? { login: reporter.login, name: reporter.name } : {},
      project: { shortName: issue.project.shortName }
    }
  };

  const connection = new http.Connection(DENLY_URL);
  connection.addHeader('Content-Type', 'application/json');
  connection.addHeader('X-Denly-Token', DENLY_SECRET);
  const response = connection.postSync('', null, JSON.stringify(payload));
  console.log('Denly response: ' + response.code + ' ' + response.response);
}

exports.rule = entities.Issue.onChange({
  title: 'Send tagged issues to Denly',
  guard: ctx => {
    const issue = ctx.issue;
    return issue.becomesReported || issue.isChanged('tags') || issue.isChanged('Assignee');
  },
  action: ctx => {
    const issue = ctx.issue;
    const tags = [];
    issue.tags.forEach(tag => tags.push(tag.name.toLowerCase()));
    const hasDenlyTag = tags.includes('denly');
    const hasAssignee = issue.fields.Assignee != null;
    if (hasDenlyTag || hasAssignee) sendIssue(issue, issue.becomesReported ? 'created' : 'updated');
  },
  requirements: {}
});

Read JetBrains HTTP handlers and YouTrack on-change rules.

4. Test and troubleshoot

Create or update an issue and add denly. In Recent events, Processed means it became a task and Skipped means the project, tag or assignee did not match. No row at all means X-Denly-Token is wrong – the delivery never reached the queue.

Denly finds your project's State field on its own. Name it in State field name only if the project renamed it or has more than one. Status mappings must use exact names such as In Progress, Fixed, or Blocked.

Linear

Import Linear issues and comments for a team with signed webhooks, workflow updates, and assignment routing.

1. Create a Linear API key

In Linear:

  1. Open Settings → API.
  2. Create a personal API key for an account that can read and update the target team.
  3. Name it Denly and copy it immediately.

Keep this API key for Denly's API key field. Do not put it in Linear's webhook form. Read Linear webhooks.

2. Create the Denly connection

Create the connection before creating the Linear webhook, because Denly generates the callback URL.

Open Workspace → Integrations → Connect → Linear and use:

  • External workspace or repository: the Linear team ID.
  • API base URL: https://api.linear.app/graphql.
  • API key: paste the Linear API key.
  • Trigger tag: denly.
  • Trigger on create: leave disabled for the first test.
  • Initial Denly stage: Plan first.

Routing is optional. Leave Default Denly assignee blank for unassigned tasks, or choose one workspace member to own everything from this connection.

For per-person routing use Route by external assignee: each row pairs one person in the provider (left) with a workspace member (right), and + adds another row. Routing follows the item's assignee in the provider, not whoever created it, and a matching row beats the default above.

The left-hand value must be the identifier the provider actually sends, which the field's placeholder names for you. Matching ignores surrounding spaces and letter case, but nothing else: a near-miss routes nothing and looks the same as a broken integration.

Create the connection and copy the generated Webhook URL. The initial Denly secret is only a placeholder for this Linear flow; Linear generates the signing secret in the next step. Linear connections comment back and move workflow states; they do not log time, so no worklog setting is offered.

3. Create the Linear webhook

In Linear:

  1. Open Settings → API → Webhooks.
  2. Select New webhook.
  3. For URL, paste the Webhook URL copied from Denly. It looks like https://your-public-host/integrations/linear/webhook/YOUR_CONNECTION_ID.
  4. Select the same target team.
  5. Subscribe to Issue and Comment resources.
  6. Create/save the webhook.
  7. Open the webhook details and copy Linear's generated signing secret.

Do not paste the signing secret back into Linear. Linear already stores it; Denly needs a copy to verify the Linear-Signature header.

4. Put Linear's signing secret in Denly

Return to Workspace → Integrations, open the Linear connection menu, and choose Edit. Find Linear webhook signing secret, paste the signing secret copied from Linear, and select Save changes.

This replaces Denly's initial placeholder secret with the secret Linear actually uses to sign deliveries. The value belongs in Denly, not in the Linear URL field.

5. Test and troubleshoot
  1. Create or update a Linear issue in the connected team.
  2. Add the denly label.
  3. Open Workspace → Integrations → Linear → Recent events.

Processed creates a task; Skipped means the team, label, assignee or create rule declined it. An empty list means the Linear signing secret stored in Denly does not match the webhook's current one, so nothing is reaching the queue – if you regenerated or replaced the Linear webhook, paste the new secret into Denly again. Map exact Linear workflow-state names.

GitHub Issues

Import repository issues as Denly tasks and route them by GitHub assignee or a default member.

1. Create a GitHub token

In GitHub open Settings → Developer settings → Fine-grained personal access tokens. Create a repository-limited token named Denly, grant issue read/write and issue-comment write permissions, generate it, and copy it immediately. Read GitHub token documentation.

2. Create the Denly connection

Open Workspace → Integrations → Connect → GitHub Issues. Use owner/repository, GitHub’s API base URL, the token, trigger tag denly, and Plan first. Keep Trigger on create disabled initially.

Routing is optional. Leave Default Denly assignee blank for unassigned tasks, or choose one workspace member to own everything from this connection.

For per-person routing use Route by external assignee: each row pairs one person in the provider (left) with a workspace member (right), and + adds another row. Routing follows the item's assignee in the provider, not whoever created it, and a matching row beats the default above.

The left-hand value must be the identifier the provider actually sends, which the field's placeholder names for you. Matching ignores surrounding spaces and letter case, but nothing else: a near-miss routes nothing and looks the same as a broken integration.

3. Add the webhook

In the repository open Settings → Webhooks → Add webhook. Set the generated URL, choose application/json, paste the Denly inbound secret as the webhook secret, subscribe to Issues and Issue comments, save, and use Recent Deliveries → Redeliver for testing. Do not use the separate GitHub App lifecycle endpoint.

4. Test and troubleshoot

Open an issue and add denly. Check Recent events. Processed creates a task; Skipped means the repository, label, assignee or create rule declined it. An empty list means the webhook secret does not match, so X-Hub-Signature-256 never verified – GitHub's Recent Deliveries tab shows those attempts and can redeliver one. Comments containing #denly revise linked tasks.

GitLab Issues

Import GitLab issues and notes from GitLab.com or self-managed GitLab.

1. Create a GitLab token

In GitLab open User settings → Access tokens or create a project access token. Name it Denly, grant api, set an expiration, create it, and copy it immediately. Read GitLab access tokens.

2. Create the Denly connection

Open Workspace → Integrations → Connect → GitLab Issues. Use the full project path, such as group/platform/api, the GitLab HTTPS base URL, token, trigger label denly, and Plan first. Leave create disabled initially.

Routing is optional. Leave Default Denly assignee blank for unassigned tasks, or choose one workspace member to own everything from this connection.

For per-person routing use Route by external assignee: each row pairs one person in the provider (left) with a workspace member (right), and + adds another row. Routing follows the item's assignee in the provider, not whoever created it, and a matching row beats the default above.

The left-hand value must be the identifier the provider actually sends, which the field's placeholder names for you. Matching ignores surrounding spaces and letter case, but nothing else: a near-miss routes nothing and looks the same as a broken integration.

3. Add the webhook

In GitLab open Project → Settings → Webhooks. Paste the Denly URL and inbound secret into Secret token, enable Issues events and notes/comments, save, and run Test → Issues events. Self-managed GitLab must be publicly reachable over HTTPS.

4. Test and troubleshoot

Add denly to an issue and inspect Recent events. Processed creates a task; Skipped means the project, label, assignee or create rule declined it. An empty list means X-Gitlab-Token is wrong; GitLab's webhook page keeps its own delivery log. Notes containing #denly revise linked tasks.

Trello

Import Trello cards, synchronize comments and lists, and route by Trello member or default member.

1. Create Trello credentials

Open the Trello developer key page. Copy the API key, open the token link on that page, authorize and copy the token, and record the API secret for webhook signing.

2. Create the Denly connection

Open Workspace → Integrations → Connect → Trello. Use the board ID, API key, token, API secret, trigger label denly, and list-ID status mappings. Leave create disabled initially.

Routing is optional. Leave Default Denly assignee blank for unassigned tasks, or choose one workspace member to own everything from this connection.

For per-person routing use Route by external assignee: each row pairs one person in the provider (left) with a workspace member (right), and + adds another row. Routing follows the item's assignee in the provider, not whoever created it, and a matching row beats the default above.

The left-hand value must be the identifier the provider actually sends, which the field's placeholder names for you. Matching ignores surrounding spaces and letter case, but nothing else: a near-miss routes nothing and looks the same as a broken integration.

Trello signs the body plus the exact callback URL, so do not rewrite the generated URL.

3. Add the webhook

Create a board webhook using Trello’s webhook API. Set its callback URL to Denly’s generated URL, set the model to the board, and subscribe to card creation, updates/labels, and comments. Read Trello webhook documentation.

4. Test and troubleshoot

Create a card and add denly. Processed creates a task and Skipped means the label or create rule declined it. An empty list means the signature did not verify: Trello signs the body together with the exact callback URL, so the webhook's URL must be the generated one character for character – a trailing slash or an http/https difference is enough to fail. A tagged card comment becomes revision work.

Asana

Import Asana tasks using the webhook handshake and route by Asana assignee.

1. Create an Asana token

In Asana open the personal access-token area, create a token named Denly, copy it immediately, and confirm the account can read the target project. Read Asana personal access tokens.

2. Create the Denly connection

Open Workspace → Integrations → Connect → Asana. Use the workspace/project identifier, token, project scope, trigger tag denly, and Plan first. Leave create disabled initially.

Routing is optional. Leave Default Denly assignee blank for unassigned tasks, or choose one workspace member to own everything from this connection.

For per-person routing use Route by external assignee: each row pairs one person in the provider (left) with a workspace member (right), and + adds another row. Routing follows the item's assignee in the provider, not whoever created it, and a matching row beats the default above.

The left-hand value must be the identifier the provider actually sends, which the field's placeholder names for you. Matching ignores surrounding spaces and letter case, but nothing else: a near-miss routes nothing and looks the same as a broken integration.

Do not invent an inbound secret: Asana supplies X-Hook-Secret during registration and Denly stores it.

3. Register the webhook

Create an Asana webhook for the target project with Denly’s generated URL. Asana sends an X-Hook-Secret handshake; Denly echoes it and stores it. Only enable production events after the handshake succeeds. Read Asana webhooks.

4. Test and troubleshoot

Add denly to an Asana task or assign it to a mapped user. Asana's event carries little more than an ID, so Denly fetches the task before it can judge it. Skipped means the fetched tags, project, assignee or create rule declined it. Failed with a permission error means the token cannot read the task. An empty list means the handshake secret is stale – re-register the webhook so Asana sends a fresh one.

ClickUp

Import ClickUp tasks and comments, synchronize statuses and time entries, and route by ClickUp assignee.

1. Copy your ClickUp token

In ClickUp open Settings → Integrations & ClickApps → ClickUp API, and on the API tokens tab press Copy. Your personal token already exists – it starts pk_ – so there is nothing to create.

Use Regenerate only if you actually need a new token: it invalidates the current one and breaks every other integration already using it.

The token acts as you, so confirm that account can read the target list and post comments – Denly fetches each task with it. Read ClickUp authentication.

2. Create the Denly connection

Open Workspace → Integrations → Connect → ClickUp.

  • Project scope: the Denly project imported tasks land in.
  • External workspace or repository: your numeric Workspace (team) ID – the first number in any ClickUp URL, e.g. 90182959236. The same value is used to post time entries.
  • Personal API token: the token from step 1.
  • Limit to a ClickUp list ID: optional, and worth setting for a first test – see step 3.
  • Trigger tag: denly; keep Trigger on create disabled for the first test.
  • Initial Denly stage: Plan first. Enable Post worklogs to have Denly record time entries.

The tag is a ClickUp tag, not a custom field, and tags are per space – so the space you test in needs a denly tag. You can create it inline from the task's Tags menu.

Routing is optional. Leave Default Denly assignee blank for unassigned tasks, or choose one workspace member to own everything from this connection.

For per-person routing use Route by external assignee: each row pairs one person in the provider (left) with a workspace member (right), and + adds another row. Routing follows the item's assignee in the provider, not whoever created it, and a matching row beats the default above.

The left-hand value must be the identifier the provider actually sends, which the field's placeholder names for you. Matching ignores surrounding spaces and letter case, but nothing else: a near-miss routes nothing and looks the same as a broken integration.

3. The webhook registers itself

Nothing to do. ClickUp has no webhook screen – its webhooks exist only through its API – so Denly creates one for you when you save the connection, using the token from step 2. It subscribes to taskCreated, taskUpdated, taskTagUpdated, taskAssigneeUpdated and taskCommentPosted, stores the secret ClickUp mints, and names the webhook it created when it saves.

Scope it while testing: fill Limit to a ClickUp list ID in step 2 and the webhook is created against that list alone. Unscoped, it delivers every task event in the workspace, and ClickUp’s event is too thin to filter on, so each one costs a task fetch before Denly can drop it. The setting is read when the webhook is created, so it only appears while you are creating the connection – to change it later, recreate the connection.

If registration fails – usually a token without webhook scope – the connection is still created and the dialog says why, then shows the URL and secret to register by hand. You can then create the hook yourself and paste ClickUp’s secret into ClickUp webhook signing secret under Edit. Read ClickUp webhooks.

ClickUp sends a thin event carrying little more than a task ID; Denly then fetches the full task with the API token, which is why the token needs read access to the target list.

4. Test and troubleshoot

Create a ClickUp task, add the denly tag, and open Workspace → Integrations → Recent events.

  • Processed – it became a Denly task.
  • Skipped – expected, and usually not a fault. Trigger did not match after enrichment means the fetched task had no denly tag. Already linked means another event for the same task got there first.
  • Failed – the diagnostic column carries the provider's own error.

Creating an already-tagged task fires two events: taskCreated builds the task and taskTagUpdated then lands on it and is skipped as already linked. Two rows for one task is correct.

Nothing at all, and the card still on Awaiting first event, means deliveries are being rejected before the queue – the signing secret does not match. Recreate the connection so a fresh webhook is registered.

A comment containing #denly on a linked task becomes revision work.

monday.com

Import monday.com board items and updates and synchronize status columns.

1. Create monday credentials

In monday.com open the developer/API area, create or copy an API token for an account that can read and update the target board, and record the numeric board ID. Read monday authentication.

2. Create the Denly connection

Open Workspace → Integrations → Connect → monday.com. Use the board ID, API token, default GraphQL URL, trigger label or status value, and Plan first. Set Status column ID only if the board does not use monday's default status column.

Routing is optional. Leave Default Denly assignee blank for unassigned tasks, or choose one workspace member to own everything from this connection.

For per-person routing use Route by external assignee: each row pairs one person in the provider (left) with a workspace member (right), and + adds another row. Routing follows the item's assignee in the provider, not whoever created it, and a matching row beats the default above.

The left-hand value must be the identifier the provider actually sends, which the field's placeholder names for you. Matching ignores surrounding spaces and letter case, but nothing else: a near-miss routes nothing and looks the same as a broken integration.

3. Add the webhook

Create a board webhook. For monday’s built-in webhook append ?t=YOUR_INBOUND_SECRET to Denly’s URL. For a monday app, configure its signing secret and use JWT verification. Subscribe to item creation, column changes, and updates. Read monday webhooks.

4. Test and troubleshoot

Create an item, set its trigger status or label, or add an update containing #denly. Skipped means the status, label or create rule declined it. Failed on a transition usually means the status column ID is wrong – set it in Status column ID if the board does not use monday's default status column. An empty list means the URL token or JWT does not match.

Basecamp

Import Basecamp to-dos and tagged comments using a URL-token webhook.

1. Create Basecamp API access

Create a Basecamp 3 API client, authorize it for the target account, and obtain an OAuth access token. Record the account ID. Read Basecamp 3 API documentation.

2. Create the Denly connection

Open Workspace → Integrations → Connect → Basecamp. Use the account ID, OAuth token, HTTPS base URL if custom, and a #denly marker in to-do text. Basecamp has no label field, so the tag is matched in text.

Routing is optional. Leave Default Denly assignee blank for unassigned tasks, or choose one workspace member to own everything from this connection.

For per-person routing use Route by external assignee: each row pairs one person in the provider (left) with a workspace member (right), and + adds another row. Routing follows the item's assignee in the provider, not whoever created it, and a matching row beats the default above.

The left-hand value must be the identifier the provider actually sends, which the field's placeholder names for you. Matching ignores surrounding spaces and letter case, but nothing else: a near-miss routes nothing and looks the same as a broken integration.

3. Add the webhook

Create a Basecamp webhook for to-do and comment activity. Append ?t=YOUR_INBOUND_SECRET to the Denly URL and subscribe to to-do created/updated and comment-created events. Basecamp webhooks are unsigned; the URL token is the authentication.

4. Test and troubleshoot

Create a to-do containing #denly and inspect Recent events. Processed creates a task; Skipped means the marker or project scope declined it, or the to-do was already linked. An empty list means the URL token is wrong. Denly drops a retry it has already handled, which is correct and also leaves no row. Basecamp connections comment back; they do not move statuses or log time.

Team chat

Slack

Create tasks from /denly commands or reactions, route them by Slack user, and receive Denly notifications.

1. Create a Slack app

In Slack apps create an app. Copy its Signing Secret, create the /denly slash command, enable Event Subscriptions with reaction_added, and install the app to copy the bot token.

2. Create the Denly connection

Open Workspace → Integrations → Connect → Slack.

  • External workspace or repository: the Slack team ID.
  • Bot token: the token from installing the app.
  • Slack signing secret: the Signing Secret from step 1. This is what every request is verified against – the secret Denly generates is unused for Slack, so a connection without it rejects everything.
  • Slash-command aliases: /denly. Optionally set a Reaction trigger (robot_face by default) and map channels to projects.

Routing is optional. Leave Default Denly assignee blank for unassigned tasks, or choose one workspace member to own everything from this connection.

For per-person routing use Route by external assignee: each row pairs one person in the provider (left) with a workspace member (right), and + adds another row. Routing follows the item's assignee in the provider, not whoever created it, and a matching row beats the default above.

The left-hand value must be the identifier the provider actually sends, which the field's placeholder names for you. Matching ignores surrounding spaces and letter case, but nothing else: a near-miss routes nothing and looks the same as a broken integration.

Map keys are Slack member IDs (U012ABCDEF), which is what command and reaction payloads carry – not display names.

3. Configure Slack URLs
  1. Set the slash-command Request URL to Denly’s generated URL.
  2. Set the Events API Request URL to the same URL.
  3. Subscribe to reaction_added.
  4. Grant channel read permissions for reaction enrichment and chat:write for notifications.

Read Slack request verification.

4. Test and troubleshoot

Run /denly fix the failing test in a mapped channel or add the configured reaction. Processed creates a task; Skipped means the command, reaction, channel or project mapping did not match. Failed on a reaction usually means the bot cannot read the message it was added to. An empty list means the Signing Secret does not match.

Microsoft Teams

Create tasks from Teams outgoing-webhook commands and route them by Teams sender.

1. Create the Denly connection

This comes first: the Teams outgoing webhook needs a callback URL, and Denly only generates that once the connection exists.

Open Workspace → Integrations → Connect → Microsoft Teams. Fill in the team or conversation ID, the Workflow webhook URL Denly posts its cards to, the command alias, and the project scope. Leave Teams outgoing-webhook secret blank for now – Teams has not generated it yet.

Routing is optional. Leave Default Denly assignee blank for unassigned tasks, or choose one workspace member to own everything from this connection.

For per-person routing use Route by external assignee: each row pairs one person in the provider (left) with a workspace member (right), and + adds another row. Routing follows the item's assignee in the provider, not whoever created it, and a matching row beats the default above.

The left-hand value must be the identifier the provider actually sends, which the field's placeholder names for you. Matching ignores surrounding spaces and letter case, but nothing else: a near-miss routes nothing and looks the same as a broken integration.

Save, then copy the generated Webhook URL.

2. Create the Teams outgoing webhook

Add an outgoing webhook to the target team, name it Denly, and set its callback URL to the Denly URL you just copied. Teams generates a base64 security token and shows it once.

Return to Workspace → Integrations → Microsoft Teams → Edit and paste it into Teams outgoing-webhook secret. Teams signs every request with it, so nothing arrives until this is stored – the secret Denly generated is unused here. Read Teams outgoing webhooks.

3. Send the command

Mention the outgoing webhook with /denly followed by the task text. Teams signs requests as Authorization: HMAC <base64-signature>. Denly must be publicly reachable over HTTPS.

4. Test and troubleshoot

Processed means Denly created the task and answered Teams; Skipped means the command, conversation or project mapping did not match. An empty list means the base64 Teams secret is wrong, so no request from Teams ever verifies.

Discord

Create tasks from Discord slash commands and route them by Discord user ID.

1. Create a Discord application

In the Discord Developer Portal create an application and collect three values:

  • Application ID and Public Key – both on General Information.
  • Bot token – on the Bot tab (Reset Token shows it once). It is only used to register the command in step 2; Denly never stores it.

Then invite the application to the target server with an OAuth2 URL that carries the applications.commands scope:

https://discord.com/oauth2/authorize?client_id=<APPLICATION_ID>&scope=applications.commands
2. Register the slash command

Unlike Slack, the Developer Portal has no screen for slash commands – they are registered against the Discord API with the bot token. Register /denly for one server (appears instantly; best while testing):

curl -X POST "https://discord.com/api/v10/applications/<APPLICATION_ID>/guilds/<SERVER_ID>/commands" \
  -H "Authorization: Bot <BOT_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"name":"denly","description":"Send a task to the Denly agent","options":[{"type":3,"name":"task","description":"What should the agent do?","required":true}]}'

Two details are load-bearing: the option must be named task (or text) – that is the field Denly reads the request from – and the command name must match the command alias you enter on the connection in step 3. For all servers at once, drop /guilds/<SERVER_ID> from the URL; global commands can take up to an hour to appear. Read Discord application commands.

3. Create the Denly connection

Open Workspace → Integrations → Connect → Discord. Use the server/channel ID, the application Public Key from step 1 (Discord signs every request with Ed25519 against it – Denly holds no Discord secret), a channel webhook URL for outbound notification cards (Channel settings → Integrations → Webhooks), the command alias (/denly – must match the name registered in step 2), and the channel/project mapping.

Routing is optional. Leave Default Denly assignee blank for unassigned tasks, or choose one workspace member to own everything from this connection.

For per-person routing use Route by external assignee: each row pairs one person in the provider (left) with a workspace member (right), and + adds another row. Routing follows the item's assignee in the provider, not whoever created it, and a matching row beats the default above.

The left-hand value must be the identifier the provider actually sends, which the field's placeholder names for you. Matching ignores surrounding spaces and letter case, but nothing else: a near-miss routes nothing and looks the same as a broken integration.

Map keys are Discord user IDs – the numeric ID from right-click → Copy User ID (Developer Mode), belonging to whoever runs the command.

Save, then copy the generated Webhook URL for the next step. The inbound secret Denly generates alongside it is unused for Discord – every request is verified against the Public Key instead – so you can ignore it.

4. Configure interactions
  1. Open the Discord application’s General Information → Interactions Endpoint URL.
  2. Paste Denly’s generated URL.
  3. Discord sends a signed type-1 PING; Denly answers it, and Discord accepts the endpoint. If Discord rejects it, the Public Key on the connection does not match the application.
  4. Save.

Read Discord interactions.

5. Test and troubleshoot

In a mapped channel run /denly task: fix the failing test. Processed creates the task and answers with an ephemeral acknowledgement; Skipped means the command name, channel or project mapping did not match. An empty list means the Public Key does not match the application. If Discord shows “the application did not respond”, the interactions endpoint is not reachable or not saved. Discord reactions are not supported – reactions arrive only over a persistent gateway connection, which Denly deliberately does not hold; commands are the supported path.

Mattermost

Create tasks from Mattermost slash commands and route them by Mattermost user.

1. Create the Mattermost slash command

In Mattermost open Integrations → Slash Commands → Add Slash Command. Choose the team, set trigger denly, set the Request URL to Denly’s generated URL, and copy the generated command token. Read Mattermost integrations.

2. Create the Denly connection

Open Workspace → Integrations → Connect → Mattermost. Use the team or server ID, the incoming webhook URL for notifications, the command alias, and the channel-to-project mapping. Paste the token from step 1 into Mattermost command token – every command is verified against it, and the secret Denly generates is unused here.

Routing is optional. Leave Default Denly assignee blank for unassigned tasks, or choose one workspace member to own everything from this connection.

For per-person routing use Route by external assignee: each row pairs one person in the provider (left) with a workspace member (right), and + adds another row. Routing follows the item's assignee in the provider, not whoever created it, and a matching row beats the default above.

The left-hand value must be the identifier the provider actually sends, which the field's placeholder names for you. Matching ignores surrounding spaces and letter case, but nothing else: a near-miss routes nothing and looks the same as a broken integration.

Map keys can be Mattermost user IDs or usernames: the payload carries both, and Denly tries each.

3. Verify the request

Save the command and run /denly fix the failing test. Mattermost sends the command token in the form body. Keep Denly on a public HTTPS URL.

4. Test and troubleshoot

Processed means Denly created the task and replied in the channel; Skipped means the command, project mapping or message text did not match. An empty list means the command token stored in Denly is not the one Mattermost generated.

Telegram

Create tasks from Telegram bot commands and route them by Telegram sender.

1. Create a Telegram bot

In Telegram open @BotFather, run /newbot, choose the bot name and username, copy the bot token, and add the bot to the target chat. Read Telegram Bot API.

2. Create the Denly connection

Open Workspace → Integrations → Connect → Telegram. Use the bot/chat identifier, bot token, command alias /denly, chat/project mapping, and optional notification chat ID.

Routing is optional. Leave Default Denly assignee blank for unassigned tasks, or choose one workspace member to own everything from this connection.

For per-person routing use Route by external assignee: each row pairs one person in the provider (left) with a workspace member (right), and + adds another row. Routing follows the item's assignee in the provider, not whoever created it, and a matching row beats the default above.

The left-hand value must be the identifier the provider actually sends, which the field's placeholder names for you. Matching ignores surrounding spaces and letter case, but nothing else: a near-miss routes nothing and looks the same as a broken integration.

Map keys are numeric Telegram user IDs – Telegram sends the sender's numeric ID with every command, so a username will not match. Each person can get their ID by messaging @userinfobot in Telegram.

3. Register the Telegram webhook

Call Telegram’s setWebhook method with the generated URL and secret:

https://api.telegram.org/bot<BOT_TOKEN>/setWebhook?url=https://your-public-host/integrations/telegram/webhook/YOUR_CONNECTION_ID&secret_token=YOUR_INBOUND_SECRET
  1. Replace the bot token, connection ID, and secret.
  2. Run the request once.
  3. Confirm ok: true.
4. Test and troubleshoot

Send /denly fix the failing test. Processed creates the task; Skipped means the command, chat or project mapping did not match. An empty list means X-Telegram-Bot-Api-Secret-Token does not match the secret given to setWebhook.

Usage, cost, budgets & insights

Understand capacity and outcomes.

Usage shows provider-reported quota and task-versus-Chat tokens. Quota is only reported by Claude Code and Antigravity; the other runtimes do not publish one. Insights shows merge rate, shipped work, pulled back (tasks a person took out of the agent's hands), build time, runtime success, tokens, and reported cost. A monthly budget warns owners but does not interrupt active work.

Notifications

Know when a task needs you, and nothing else.

Denly emails you when work stops and waits for a person. Each kind is a separate switch in Settings → Email notifications, and the master switch at the top turns every kind off without losing your individual picks.

  • A task needs your input – the agent asked a question and is blocked. On by default.
  • A plan is ready to approve – nothing continues until somebody signs it off. On by default.
  • A pull request needs review – off by default: every finished task lands here, so it is opt-in.
  • A task failed – a run ended badly and may need a retry. On by default.
  • Weekly digest – one summary email a week. On for owners, opt-in for everybody else.

Owners also receive the workspace-level warnings: a crossed budget, and a runner that has gone offline with work waiting for it. Neither interrupts anything that is already running.

Every email carries an unsubscribe link that works without signing in, and turns off notification email for that account. Turn it back on in the same place you turned it off.

A chat connection with notifications enabled delivers the same events into its channel, so a team can watch a project without each member subscribing by email.

Plans & billing

What each plan covers, what is metered, and what happens when you cross a line.

One plan covers your whole account: every workspace its purchaser created, not just the one that is open. Inference is never part of it – your agent vendor bills that directly.

FreeProTeams
Price$0$10 / month, $100 / year$12 per seat / month, $120 / year, 2-seat minimum, 14-day trial
People11the seats you buy
Owned workspaces1uncappeduncapped
Projects, tasks, documentsUncapped on every plan.
Machines2uncappeduncapped
Enrollments per machine4 (temporary, see below)uncappeduncapped
Executionone task at a time per machineparallelparallel
Schedules5 per workspaceuncappeduncapped
Documents + attachments250 MB5 GB pooled10 GB + 2 GB per seat, pooled

What counts as a machine

A machine is one computer you have enrolled – the device, not the projects on it. Setting up a second project on a laptop you already run does not use a second machine; it uses a second enrollment on that machine.

Why enrollments are capped, and when they stop being

Each enrollment runs its own polling process today, so a free machine with twenty of them costs twenty idle pollers. The ceiling is temporary: one coordinator process per machine is being built, and the ceiling is removed when it is the default install everywhere. It never applies to a paid plan.

One task at a time, on Free

A free machine runs one task at a time, whatever it is: a plan, a build, a review, or a Live Chat session, which holds the machine while it is open. Answering a question or approving a plan on a running task costs nothing. If work is queued and nothing seems to start, end the Chat session to free the slot.

Seats are people

On Teams a seat is one person, however many of the account's workspaces they are in. Somebody in three of your workspaces is one seat.

Crossing a line

Nothing is deleted and nothing is frozen for storage: new uploads and document saves are refused, and everything already there stays readable and keeps running. A task already running never fails on a quota – the agent's own document writes are counted but never blocked, so the next save you make from the app is where you see the overage.

Pro and Teams can be billed monthly or yearly. The interval is chosen at checkout and changed from the billing portal, which also has invoices, payment methods, and cancellation.

Security, encryption & data

Know exactly which boundary protects each kind of data.

Repository contents are not uploaded to Denly. When workspace E2E is enabled, task text, plans, conversations, and attachments are encrypted in the browser or runner and unreadable to Denly. Without E2E, coordination text is stored normally so the service can deliver it.

Runner environment
DENLY_E2E_PASSPHRASE=your-workspace-passphrase
Passphrase loss is permanent.Denly cannot recover encrypted history. Your Git host receives pushed code and your chosen AI provider receives the context sent by its CLI under your agreements with them.

Your data

Take a copy, see what Denly records, or leave.

Export. Settings → Account → Your data downloads everything Denly holds about your account as JSON – your profile, workspace memberships, and the tasks you created.

Deleting your account removes it along with any workspace you alone own. In a shared workspace the work stays and your membership goes. Deleting cannot be undone, so the screen asks you to type a confirmation first.

Agent output. Record agent output on the Defaults screen decides how much of a run Denly keeps: Full records the messages and the tools it ran, Text keeps the messages and drops the tool results, and Off records nothing beyond the task's own events. It can be set per project.

Diagnostics. Asking a runner to diagnose itself uploads a redacted, curated summary of its local log – never the raw file, and never repository contents.

Runner CLI reference

Manage enrollment and lifecycle from one cross-platform CLI.

CommandPurpose
denly loginSign this machine into a workspace.
denly install <project>Clone a Git repo or create a no-Git folder at ~/Denly/<project-id>, then enroll, start, and verify it.
denly install <project> --path <folder>Adopt an existing Git checkout, or create/adopt that exact no-Git folder.
denly install --group <group>Install every project assigned to a group runner.
denly groupsList available project groups.
denly doctorCheck platform, service, credentials, runtimes, and connectivity.
denly start|stop|restart --allApply a lifecycle action to all installed runners.
denly projects|status|logsInspect local and cloud state. --all works on status, not on logs.
denly install-allInstall every project this machine can reach, in one pass.
denly install <project> --dir <parent>Clone into a parent directory instead of naming the folder itself. Mutually exclusive with --path.
denly whoamiWhich account and workspace this machine is signed into.
denly authmodeHow each installed agent CLI is authenticated here.
denly set-key / clear-keyStore or remove an API key in the machine's own secret store.
denly logoutSign this machine out. Installed runners stop being able to claim.
denly doctor --all|--runtime <id>|--no-probeCheck every project, one runtime, or skip the network probes.
denly --versionThe runner build on this machine.

Add --json to projects, groups or whoami for machine-readable output.

Troubleshooting

Trace problems from connection to execution.

  • Queued task: confirm scope, assignment, and a live eligible runner.
  • Offline runner: run denly doctor, then inspect status and logs.
  • Integration failure: filter Recent events by failed state and inspect the redacted diagnostic.
  • Rejected credential: re-run the project or group install to rotate and verify it.
  • Missing runtime: authenticate the vendor CLI locally and restart the runner.
No documentation matched. Try runner, task, integration, or security.