Approvals
Ask a person to say yes before your app does something risky, then carry on or stop depending on the answer.
Some actions should have a human nod first: deleting rows, spending money, emailing a customer list. With approvals, your app asks. A person answers. Your app then carries on or stops.
Think of a restaurant order slip. The waiter writes the order and hangs it in the kitchen window. The waiter does not stand there until the food is ready. They go and serve other tables, and the kitchen rings a bell when the slip is done. An approval works the same way. It is a stored request, not a paused app. Your app asks and gets an id back at once. The id is the slip number. Your app can stop there, keep working on other things, or come back later to check.
How it works
- Your app asks. The platform stores the request and gives your app an
id. - The platform tells the people who may decide: in the dashboard, in the command line, by email, or in Telegram if they linked it, or through an AI assistant acting for them.
- A person answers: approve or deny. If nobody answers in time, the request expires, and that counts as a no.
- Your app finds out. Either the platform calls your app back (waking it if it is asleep), or your app checks by
id. - Your app does the action itself. An approval is a yes or a no. It never runs anything for you.
Careful Treat every answer except
approvedas "do not do it". That includesdenied,expiredandcancelled.
When to use it
Use approvals when an action is hard to undo, costs money, or reaches other people. Good examples:
- Deleting or changing a lot of data.
- Sending an email to a list of customers.
- Anything an AI assistant decides on its own that you want a person to check.
Do not use approvals for steps that are safe and quick. Every request is something a person has to read.
Turn it on
Declare bindings.approvals in cohostly.json, then deploy.
{ "bindings": { "approvals": { "enabled": true, "callback_path": "/__platform/hooks/approvals/decision" } } }
enabledtrue or falseapprovals object that does not set enabled to false counts as declared, so even {} turns the binding on.callback_pathtext/, and it can be at most 200 characters. It is the only path the platform will ever call for decisions, and a request cannot name a different one. Leave it out if your app will check by id instead.Declaring the binding gives your app one setting:
APPROVALS_URL/approvals. Not secret.Good to know
/__platform/hooks/approvals/decisionis a convention, not a default. The SDK does not mount any path for you. You mount the handler yourself on whatever path you registered. See callbacks and waiting.
A first request
import platform from './platform.gen.js' const approval = await platform.approvals.request({ summary: 'Delete 12 rows from the orders table', // one plain sentence, shown as written details: { table: 'orders', rows: 12 }, // the facts, shown as written risk: 'medium', // low, medium or high action: 'delete_rows', // a short key that rules can target expiresIn: 3600, // seconds, 60 to 604800. Default 24 hours }) console.log(approval.id, approval.status) // "pending"
Write summary and details so a person can decide without opening your code. The approval covers exactly that and nothing else. The next page covers every field, the Python and plain HTTP versions, and the answer you get back.