This tutorial covers the Trigger Inbox API connected to an app using the user’s app connection. It follows the same queue model as Trigger Inbox quickstart, adding connection setup before inbox creation.
Creating an inbox for an app needs three values from the Workflow API, all authenticated with the same $TOKEN as the Trigger Inbox API, plus your trigger’s input fields:
app_key: the app’s key, from GET /v2/apps. Accepts the versionless key (e.g. SlackCLIAPI), a slug (e.g. slack), or @latest suffix (e.g. SlackCLIAPI@latest). All resolve to the latest version automatically.
action_key: the trigger’s key, from GET /v2/actions. Identifies which trigger the inbox subscribes to.
connection_id: the ID of the user’s connected account, from GET /v2/authentications.
List apps and read the key field. The query parameter narrows the list by app title, but returns partial matches, you may get more than one result. Match the title field to identify the correct app.
List the app’s read actions with GET /v2/actions, using the app id from the previous step. The action key identifies which trigger the inbox subscribes to. Scan the response for the trigger you want by its human-readable title, then read its key. That key is your action_key.
Each action has a human-readable title, a stable key, and an opaque id:
{ "data": [ { "id": "core:AZko0fdDdRnYyOQ3EDpQO2154JW9JK", "key": "channel_message", "type": "action", "action_type": "READ", "is_instant": true, "title": "New Message Posted to Channel", "description": "Triggers when a new message is posted to a channel." } ]}
Match your trigger by title, then use its key (for example, channel_message) as your action_key. Replace ACTION_ID in the next steps with the id value from the response.
Each action’s id is its action ID: the opaque, encoded value you pass as the {action_id} path parameter in later requests. It is distinct from action_key: action_key is the stable key you put in the inbox subscription (Step 2), while the action ID is this opaque id you put in /v2/actions/{action_id}/... URLs. It is not something you can construct. It is also not stable: the same action returns a different id on each GET /v2/actions call, though any id you capture stays valid for later requests. Capture it now. You need it to list input fields in Step 4 (POST /v2/actions/{action_id}/inputs), and the optional schema preview below uses it too.
List the user’s authentications for your app. Replace APP_ID in the request with the app id from the app key step. Each authentication represents a connected app account, with a UUID id and an app field holding the app’s UUID.
const APP_ID = "81f613aa-c98a-4383-a5fc-195e68647217"; // app id from the app key stepconst res = await fetch(`https://api.zapier.com/v2/authentications?app=${APP_ID}`, { headers: { Authorization: `Bearer ${TOKEN}` },});const { data } = await res.json();const connection = data.find((auth) => !auth.is_expired);console.log("connection_id:", connection?.id);
APP_ID = "81f613aa-c98a-4383-a5fc-195e68647217" # app id from the app key stepresp = requests.get( "https://api.zapier.com/v2/authentications", params={"app": APP_ID}, headers={"Authorization": f"Bearer {TOKEN}"},)resp.raise_for_status()connection = next(a for a in resp.json()["data"] if not a["is_expired"])print("connection_id:", connection["id"])
Match the authentication whose app equals the app id from the previous step, then use its id as your connection_id. Replace CONNECTION_ID in the next steps with this id.
The authentications endpoint requires the app’s UUID (id), not the app_key string. Use the app id from the app key step here, and app_key in the subscription request in Step 2.
A trigger accepts input fields, for example, which channel to monitor. List them with POST /v2/actions/{action_id}/inputs, using the action ID and connection_id from the previous steps. Pass empty inputs ({}) to get the initial set of fields.
const ACTION_ID = "core:AZko0fdDdRnYyOQ3EDpQO2154JW9JK"; // id from the action key stepconst CONNECTION_ID = "019487c8-6b2a-7c1e-9f3d-2a1b0c4d5e6f"; // id from the connection stepconst res = await fetch( `https://api.zapier.com/v2/actions/${ACTION_ID}/inputs`, { method: "POST", headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ data: { authentication: CONNECTION_ID, inputs: {} }, }), });const { data } = await res.json();console.log( data.map((f) => ({ id: f.id, title: f.title, is_required: f.is_required })));
ACTION_ID = "core:AZko0fdDdRnYyOQ3EDpQO2154JW9JK" # id from the action key stepCONNECTION_ID = "019487c8-6b2a-7c1e-9f3d-2a1b0c4d5e6f" # id from the connection stepresp = requests.post( f"https://api.zapier.com/v2/actions/{ACTION_ID}/inputs", json={"data": {"authentication": CONNECTION_ID, "inputs": {}}}, headers={"Authorization": f"Bearer {TOKEN}"},)resp.raise_for_status()for field in resp.json()["data"]: print(field["id"], field["is_required"], field["title"])
Each input_field has an id (the key you use in inputs), a title, and is_required:
{ "data": [ { "type": "input_field", "id": "channel", "default_value": "", "depends_on": [], "description": "Only channels and MPDMs you are a member of will appear in this list.", "format": "SELECT", "invalidates_input_fields": false, "is_required": true, "placeholder": "", "title": "Channel", "value_type": "STRING" } ]}
Build your inputs object from the field ids and the values you want, for example, {"channel": "C0123456789"}.
Some fields are dynamic: they appear only after a connection or another field is set (check each field’s depends_on). To reveal them, send the values you have back as inputs and call again. The response may also include info_field entries (display-only) and fieldset entries (groups of related fields).
Carry these four values into Step 2:
Inbox subscription field
Source
Example
app_key
GET /v2/apps → key
SlackCLIAPI
action_key
GET /v2/actions → key
channel_message
connection_id
GET /v2/authentications → id
019487c8-6b2a-7c1e-9f3d-2a1b0c4d5e6f
inputs
POST /v2/actions/{action_id}/inputs → field ids
{"channel": "C0123456789"}
Optional: Preview the data your inbox will produce
Before you create the inbox, you can preview the fields its messages will contain. The trigger’s declared output schema covers the core fields that land in each message’s payload (real payloads can include additional fields, as the note below explains). It is available from the Workflow API using the action ID you captured in Step 1. No inbox or live data required.Post the action_id to the outputs endpoint with empty inputs to get the declared schema. Passing the connection_id and the actual inputs you’ll use (from the steps above) sharpens the result. Both keys are required: authentication may be null and inputs may be {}, but neither key can be omitted.
This is the trigger’s declared schema, not fields inferred from this inbox’s received messages. Set fetch_live_samples to true to populate sample values with real data from the app through your connection. A real-data fetch needs a valid authentication and the inputs the trigger reads from (for example, a channel); without them it usually returns nothing and falls back to the declared samples. Setting fetch_live_samples to true always performs the fetch, even when empty inputs mean it returns nothing useful, and each fetch adds noticeable latency and counts against a rate limit. It is not available for write actions. Live results may also include fields beyond the declared schema.
Perform an action in the connected app to produce a trigger event. For example, if you subscribed to Slack new messages, post a message to the monitored channel.The event will appear in the inbox within a few seconds to a few minutes, depending on the trigger type.
Each message in results[] contains the trigger payload in payload. If possible_duplicate_data is true, deduplication was not possible and your processing logic must be idempotent.