Automations
An automation tells someone when something happens in a project. People make, change and test them on the Automations page, in the sidebar (Settings, Project, Automations leads there too), or at /api/v1/automations. An editor of a brand (the project’s Editor role, or a write grant on the brand) manages the automations for that brand’s releases; the project’s admins manage every one, and alone make those for any brand and every other trigger, since those reach private assets, brands and the audit log. Each person sees the ones they may manage.
- A brand is released (
brand.released): any brand of the project, or one named bybrand. Each release fires once. - An asset’s license is about to end (
asset.expiring):daysdays before an approved asset’s last day of use (its rights’expires;0is the day itself). Each asset fires once per last day: a license renewed to a new date fires again as that one nears. Only the current version of a stack counts, and the check runs every five minutes. - Governance changes (
governance): an entry of the project’s audit log, or a decision about an asset, named inactions. Name an action exactly (grant.set,key.created) or a family with.*(share.*); leaveactionsempty for every change, which makes the webhook a feed for your security tools. Asset decisions areasset.approved,asset.rejected,asset.archived,asset.unarchived,asset.made_current,asset.deleted,asset.restored,asset.accepted_tags,asset.dismissed_tags,asset.accepted_fieldsandasset.dismissed_fields. Entries outside a project (sign-ins, SSO, email domains, the organization itself) and a private asset’s decisions never fire: a project’s admins may not be allowed to see them. - The use check keeps refusing an asset (
check.refused): one asset refusedtimestimes (1 unless said) within the lastdaysdays (7 unless said). Fires at most once per asset in each window ofdays. It counts what Insights records, so it stays quiet where usage insights are turned off. - Something new to fix (
fix.opened): what Fixes lists and isn’t done yet, ofkind:stale(a place still loads a replaced version) orgap(a search found nothing); leavekindout for both. Each fires once, and only what was first seen after the automation was made, so making one doesn’t send the backlog. - A proposal waits for review (
review.waiting): an asset in review fordaysdays or more, once each. Private assets don’t fire.
The sweep looks for refusals, Fixes and waiting proposals every five minutes; governance changes fire as they happen.
-
Email (
email): to the addresses inemails, through the organization’s email settings, counted in its emails for the day. -
Webhook (
webhook): aPOSTto anhttpsurl, informat:json(the default): the run’s JSON, below.slack: a message for a Slack incoming webhook (https://hooks.slack.com/services/...): one line saying what happened, with a link.teams: an Adaptive Card for a Teams workflow started by “When a Teams webhook request is received”, the same line and link.
The URL is kept sealed and never shown again; the automation shows its host. Addresses on private networks are refused, and redirects are not followed.
curl -X POST https://artbucket.example.com/api/v1/automations \ -H "Authorization: Bearer $ARTBUCKET_KEY" -H "Content-Type: application/json" \ -d '{"name":"Rebuild the docs","trigger":"brand.released","brand":"default","action":"webhook","url":"https://ci.example.com/hooks/brand"}'Every share link and new API key, to a Slack channel:
curl -X POST https://artbucket.example.com/api/v1/automations \ -H "Authorization: Bearer $ARTBUCKET_KEY" -H "Content-Type: application/json" \ -d '{"name":"Security","trigger":"governance","actions":["share.*","key.created"],"action":"webhook","format":"slack","url":"https://hooks.slack.com/services/T000/B000/XXXX"}'The answer holds secret, once. Keep it: every POST is signed with it.
Recipes
Section titled “Recipes”The Automations page starts one from a recipe: everything filled in but who it tells. Each is an ordinary automation, as the API takes it:
| Recipe | Body, before emails or url |
|---|---|
| New share links to Slack | {"trigger":"governance","actions":["share.created","share.project","asset.published"],"action":"webhook","format":"slack"} |
| Access changes to the admins | {"trigger":"governance","actions":["grant.*","group.members","key.created","key.revoked"],"action":"email"} |
| Licenses ending, to legal | {"trigger":"asset.expiring","days":30,"action":"email"} |
| An asset the check keeps refusing | {"trigger":"check.refused","times":3,"days":7,"action":"email"} |
| New Fixes to Teams | {"trigger":"fix.opened","action":"webhook","format":"teams"} |
| Nudge reviewers | {"trigger":"review.waiting","days":3,"action":"email"} |
| Approvals and rejections to Slack | {"trigger":"governance","actions":["asset.approved","asset.rejected"],"action":"webhook","format":"slack"} |
| Rebuild a site on each release | {"trigger":"brand.released","action":"webhook"} |
The webhook
Section titled “The webhook”{ "id": "6a1d...", "event": "brand.released", "brand": { "id": "...", "slug": "default", "name": "Acme" }, "release": 12, "note": "Bluer primary", "url": "https://artbucket.example.com/brands/default", "at": "2026-10-14T09:30:00.000Z", "automation": { "id": "...", "name": "Rebuild the docs" }, "project": { "id": "...", "slug": "library", "name": "Library" }}asset.expiring sends asset (id, filename, title), expires (the last day of use), days (left until it) and the asset’s url instead of the brand and release. The others send, beside id, event, url, at, automation and project:
| Event | Sends |
|---|---|
governance |
action, target (what it was done to), actor, agent (done with an API key), detail (as the audit log keeps it) |
check.refused |
asset, refusals, days |
fix.opened |
kind; for stale: asset, replacement, place (a host, or an agent’s key name), fetches; for gap: q, searches |
review.waiting |
asset, days (how long it has waited) |
id is the run’s: a retry sends the same body, so a receiver can tell it from a new event. |
Headers: Artbucket-Event names the event, and Artbucket-Signature signs the body (in every format; Slack and Teams ignore it) as Stripe signs its webhooks: t={unix seconds},v1={hex HMAC-SHA256 of "{t}.{body}"} with the secret. Check it before trusting the body, and refuse an old t:
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(body, header, secret) { const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("="))); const expected = createHmac("sha256", secret).update(`${t}.${body}`).digest("hex"); const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300; return fresh && v1?.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected));}Send a test
Section titled “Send a test”Send a test, on each automation, sends it now with a sample event, on or off, so a webhook or an inbox can be checked without releasing anything:
brand.released: its brand’s latest release (the default brand’s, for an automation on any brand); release 1 for a brand never released.asset.expiring: the approved asset whose license ends soonest, else the newest approved asset, as if it ended indaysdays.governance: the latest entry of the project’s audit log it takes, else one made up from its first action.check.refused: that same asset, refusedtimestimes.fix.opened: the first open Fix of its kind, else a made-up search.review.waiting: the oldest proposal, else the oldest asset.
What it sends is the real thing’s, with "test": true. A test is tried once, never again, and lands in the run log marked Test. The audit log keeps who sent it.
curl -X POST https://artbucket.example.com/api/v1/automations/{id}/test \ -H "Authorization: Bearer $ARTBUCKET_KEY"It answers the run, delivered or failed with the error, and a 422 when there is nothing to make a sample from (no brand, or no approved asset).
Runs and retries
Section titled “Runs and retries”Each thing that fires an automation is a run, delivered at once. A webhook answering 2xx, or every email sent, delivers it. Anything else is tried again 5 minutes later, then an hour after that: three attempts in all, then the run is failed. An automation turned off fires nothing, and its runs waiting for a retry wait with it.
The run log, under each automation on the Automations page and at GET /api/v1/automations/{id}/runs, keeps its latest 50 runs: what set each off, and every attempt with the webhook’s status or the error. Making, changing and deleting an automation is in the audit log.