atteq API
  • Guides
  • Recipes
  • Cookbook
  • API Reference
How it fits togetherHow to write an agentQuickstartAuthenticationFor agentsErrorsTools and their parametersHTTP tools and connections
powered by Zudoku
Guides

How it fits together

Read this page before the API reference. The reference lists two hundred operations; this page says which few you need for a given job, and in which order. Every rule here is enforced by the API, so a design that breaks one is refused, not warned about.

The model

This platform builds conversational agents from a small set of pieces, and almost every request is met by composing those pieces, not by writing code.

An agent is a graph of nodes. Each node holds instructions and the tools the model may call while it is there. Everything the model can do is a tool, and the model sees every tool the same way, whatever it is underneath.

A tool does one thing: query a collection, write a record, send an email, call one endpoint of the client's own system. A tool never carries a credential. Authorization is a connection, made once per provider and pointed at by any number of tools. A tool never carries a business fact. Opening hours, prices and policies are records in a collection (custom data), read by the tool that needs them.

When a job needs more than one tool, or a branch, it is a flow: a deterministic graph of steps, where each step is a tool and each edge is a condition on what earlier steps returned. A flow is itself a tool, so an agent calls it by one name. When the job must happen with no conversation at all, on a schedule, when a record changes, when an interaction ends, or when the client's system calls us, it is an automation: a trigger plus a flow.

A custom tool is code, written for one account, that reshapes data: it is handed arguments and answers a value. It cannot reach the network or any system. It exists for the one calculation or format nothing else provides, and its input is what other tools answered, so it sits in a flow as one step among the others: the flow hands it the price that was looked up or the list that was fetched, exactly. An agent that reads a value with one tool and types it into the next is the shape a flow replaces.

So, faced with a requirement: put what the agent should say in its node instructions; put the facts in custom data; put the credentials in a connection; give each action its own tool; combine and branch with a flow; schedule with an automation; write a custom tool only for a transform no piece provides. Never write one tool that does the whole job.

The pieces

PieceWhat it isWhat it is not
AgentA graph of nodes, each with instructions and tools. Reached through a channel: voice, text, messaging, API. Edited as a draft, published as a version.Not a "voice agent" or a "text agent". One agent, many channels.
NodeOne situation in a conversation: its instructions and the tools available there. The model moves between nodes by deciding, not by conditions.Not a step in a procedure. Procedures are flows.
ToolOne action with a name and a schema. Built-in, imported from an MCP server, a flow, or custom code: the model cannot tell. Configured as a named instance, attached globally, to a node, or as a pre/post hook.Not a place for credentials or business facts.
ConnectionOne authorization to one outside provider: a pasted key, a messaging account, or the client's own system's key (provider http_key). Pointed at by tools.Not a tool. It does nothing by itself.
Custom dataCollections of records with a declared schema, owned by the organization. Each collection comes with its own query_, get_ and write_ tools the moment it is created.Not a document library. Documents the agent quotes from are RAG.
FlowA directed graph of steps. Each step runs a tool; edges branch on conditions over earlier results; later steps read earlier outputs. Exactly one entry step; every run ends.Not conversational. The model never decides inside a flow.
AutomationA trigger plus a flow. A schedule, a record written, an interaction ended, or an outside system calling a webhook; or run by hand. Created switched off.Not a second workflow engine. It only decides when.
Custom toolStarlark code for one account: one transform(arguments) function that returns a value. Draft, publish, rollback, like an agent.Not an integration. Its declared reach must be empty.
ChannelWhere messages from contacts arrive (WhatsApp first). Every message runs the channel's own flow, which may hand it to an agent.Not a trigger for automations.

The ladder

When a requirement arrives, walk this list from the top and stop at the first rung that meets it. Each rung is cheaper to build, cheaper to change, and more visible to the client than the one below it.

  1. Node instructions. What the agent should say or how it should behave with no lookup at all. "Be brief", "always confirm the date", "hand off to a person if asked" belong here, together with the built-in tools for call control (transfer, hang up).
  2. Custom data. Any fact of the business: hours, prices, locations, the list of services, the people on shift. Create a collection with a schema; its query, get and write tools appear with it, and the agent reads what it needs at run time. A fact written inside a tool or a prompt is a fact that goes stale the day it changes.
  3. Connection. Any credential: a provider key, a messaging account, the API key of the client's own system. Create the connection once and point tools at it. A tool whose configuration carries a key or an auth header is refused when it is created.
  4. One tool per action. Look up availability. Create the booking. Send the email. Call this one endpoint. Each is a named instance, reusable across agents. The HTTP tool, pointed at a connection, is how the client's own system is reached. A single tool that reads, decides and writes is the shape to avoid: it cannot be tested, reused, or rolled back one part at a time.
  5. Flow. As soon as the job is "do this, then that, unless…". Steps are the tools from rung 4; edges branch on their results; the flow's result is the last step's output. The flow is then registered as a tool with one name, so the agent calls it as a whole and the model never sees the steps. Two tool calls the model would make one after the other, handing the first's answer to the second, are a flow too: the flow passes the value exactly, with $ref, where a model copies it. A prompt is not a place to describe a procedure; a flow is.
  6. Automation. As soon as the job has a when and no conversation: every morning, when a record is written, when an interaction ends, when the client's system calls us. The automation supplies the trigger; the flow from rung 5 supplies the work.
  7. Custom tool. Only for a transform no rung above provides: a discount formula, a file format nobody else has, a slot calculation over two lists. It is handed arguments and answers a value; it reaches nothing. It drops into a flow as one step, between the tools that fetched its input and the tool that acts on its output. A quote from a price list with a discount formula is a flow of two steps, query then formula, registered as one tool; not a custom tool on the node.

If a requirement seems to need something below rung 7, the platform cannot do it today. Say so; do not build around it.

One versus the other

When you are choosing betweenReach forBecause
A flow and the node graphThe node graph when the model should decide from what the person says; a flow when data should decide.Conversation-level branching is LLM-driven. Inside-a-tool branching is deterministic. They never compete.
A flow and a custom toolA flow whenever the steps are existing tools.Custom code fills a gap in the toolbox; it does not replace the toolbox.
A custom tool on a node and in a flowIn a flow, after the tools that fetch its input.The model never carries one tool's answer into another by hand. A flow passes it exactly with $ref, and the model sees one tool with one name.
An HTTP tool and a custom toolThe HTTP tool for anything that calls a system.A custom tool cannot make a request. The HTTP tool, pointed at a connection, is how the client's own system is reached.
A connection and a tool settingThe connection for anything secret.A tool never carries a credential. The rule is enforced on create and update.
Custom data and RAGCustom data for facts the agent reads and writes as fields; RAG for documents it quotes from.A price list is fields. A brochure is a document.
Custom data and the promptCustom data for anything that will change.The prompt is republished; a record is edited.
An automation and a flow called by the agentThe automation when there is no conversation.The same flow runs either way. Only who starts it differs.
An automation and a channel flowThe channel flow for anything that reacts to an incoming message.A message on a channel already runs a flow. It is not an automation trigger.
A pre hook and a flow stepA pre hook for what the agent should know before the first word: the caller's record, their last order.Pre hooks run once per conversation and inject context; flows run when called.
A post hook and an automation on interaction endedEither; the automation when the work has branches or several steps.Post hooks take one tool; automations take a flow.

What never goes inside a tool

  • A business fact. Opening hours hardcoded in a tool are wrong the first holiday. They are records in a collection.
  • A credential. An API key written on a tool is readable by anyone who can read the tool. It is a connection.
  • A second action. A tool that reads and then writes cannot be tested or reused apart. It is two tools and a flow.
  • A decision. "If there is a slot, book it, else say no" is an edge in a flow, not a branch in code.
  • A schedule. "Every Monday" is an automation trigger, not a loop.

A worked example

"The agent must book appointments and must never double-book." The client's bookings live in their own system, behind an API key.

The shape to avoid is one tool that authenticates, filters out the hours the business is closed, checks availability, and books. Nothing in it can be changed, tested or reused apart from the rest, and the hours are wrong the first holiday.

The composition:

  1. A connection with provider http_key holding the booking system's key and the header it travels in.
  2. A collection opening_hours with the business's hours as records.
  3. Two HTTP tools, both pointing at the connection: one fetches the day's bookings, one creates a booking.
  4. A custom tool free_slots that takes the hours and the day's bookings and answers the free slots. Pure calculation, no I/O.
  5. A flow book_appointment: query hours, fetch bookings, compute free slots, then an edge on the result. If the requested time is free, create the booking; otherwise the flow ends with the alternatives, and the agent says the time is taken.
  6. The flow registered as a tool and attached to the node where booking happens. The model sees one tool, book_appointment, with a date and a time.

The exact sequence of operations is the recipe Book an appointment without double-booking.

One key per person

Where the client's system gives each person their own key, make one connection per person, each with the same group and the person's subject_number (an international number) or subject_ref (their id in the client's system). Point the tool at the group with connection_group instead of at one connection_id. Each time the tool runs, the platform picks the connection of whoever the conversation is with. No match fails the tool before anything is sent; there is never a fallback to another person's key.

On a call, the person is identified by the phone number alone: the caller ID on an inbound call, the number dialled on an outbound one. A subject_ref never identifies anyone on a call, whatever the call's context says.

The caller ID on an inbound call is trusted as who is calling, and it can be spoofed. Anybody who presents a salesperson's number when ringing your line runs a group tool with that salesperson's key. Put a group tool on an inbound agent only where that is acceptable.

A grant per roster entry

The other shape is one connection with per_person: true, holding one grant for each person on the organization's roster. A tool pointing at it runs with the grant of whoever the conversation is with. Do both steps from your server with an organization API key made by an owner or an admin; a member's key reaches only its creator's own entry and adds no one.

First find the employee's entry by their phone. list_roster returns every entry with its addresses, phones in international form; look for the number there:

TerminalCode
curl https://YOUR_PLATFORM_ORIGIN/api/v1/roster \ -H "Authorization: Bearer $ATTEQ_API_KEY"

If nobody has it, add them. The answer's id is the entry id:

TerminalCode
curl -X POST https://YOUR_PLATFORM_ORIGIN/api/v1/roster \ -H "Authorization: Bearer $ATTEQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"display_name": "Ana Ruiz", "addresses": [{"kind": "phone", "value": "+34600123456"}]}'

A number already on another entry is refused with 409: one number is one person. An entry made this way is someone the organization vouches for, with no account and no role. Everyone with an account is on the roster already, with membership_id set; that entry follows its account and no API call links, renames or removes it. update_roster_entry renames an entry, add_roster_address, change_roster_address and remove_roster_address change its numbers, and delete_roster_entry stops vouching for somebody with no account, taking their grants with them.

Then push their key:

TerminalCode
curl -X PUT https://YOUR_PLATFORM_ORIGIN/api/v1/connections/$CONNECTION_ID/grants/$ROSTER_ENTRY_ID \ -H "Authorization: Bearer $ATTEQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{"grant_secret": "{\"header\": \"X-Api-Key\", \"prefix\": \"\", \"value\": \"ana-s-key\"}"}'

Calling it again replaces the key. remove_person_grant takes it away, and list_person_grants says which entries hold one and whether each still works. The secret is never returned.

The words the API uses

ConceptOperationsThe fields that matter
Connectioncreate_connectionprovider (http_key for the client's own system), account_label, grant_secret (for http_key: a JSON string {"header", "prefix", "value"}), scopes (empty for http_key), granted_via: "pasted". The secret is never returned.
Roster entrylist_roster, get_roster_entry, create_roster_entry, update_roster_entry, add_roster_address, change_roster_address, remove_roster_address, delete_roster_entrydisplay_name; addresses, each {kind: "phone", value}, the value returned in international form; membership_id, set only on an account's own entry, which is never created, linked or removed through the API.
Grant per roster entryset_person_grant, remove_person_grant, list_person_grantsOn a connection created with per_person: true: the path names the roster_entry_id; the body is grant_secret, as on create_connection. The secret is never returned.
HTTP toolcreate_tool, test_toolname, description, input_schema, config.endpoint, config.method, connection_id. Never an auth header in config.headers: refused.
Built-in toollist_builtins, create_tool, update_toolsource: "builtin" and config.builtin naming an entry of list_builtins, plus the connection_id that entry's provider needs. No input_schema: a built-in's parameters are the deployment's current ones, so an instance accepts what that built-in accepts today, whenever it was created. fixed_parameters holds what a person decided, hidden from the model; an update_tool that names it replaces the whole set. Tools and their parameters is the page for this.
Collection and its toolscreate_collection, create_record, update_collection, list_toolsFields with a type and a personal flag; identity_field_key set by update_collection once fields exist. The tools query_<slug>, get_<slug>, write_<slug> appear in list_tools, and their ids come back on the collection as query_tool_id, get_tool_id, write_tool_id.
Flowcreate_flow, add_step, add_flow_edge, set_entry_stepA step is {tool_id, param_overrides}; an override value "$ref:<step_id>.<path>" reads an earlier step's output, "$ref:trigger.<key>" reads what the run was started with. An edge is {source_step_id, target_step_id, condition: {field: "<step_id>.<path>", op, value}} with ops ===, !==, >, <, includes, exists. The first step added is the entry step.
Flow as a toolcreate_tool, test_toolsource: "builtin" and config.flow_id, nothing else in config: no endpoint, no connection, because the platform runs the flow itself. input_schema is what the agent fills in; the steps read it as $ref:trigger.<key>. The resulting tool id is what nodes and hooks attach. Tools and their parameters has the body.
Automationcreate_automation, set_automation_enabled, run_automationtrigger_kind is one of schedule (trigger_config.cron), record_written (trigger_config.collection_id), interaction_ended (trigger_config.agent_id, optional), webhook. flow_id names the work. Created disabled; enable it, or run it by hand.
Custom toolcreate_custom_tool, try_custom_tool, publish_custom_toolpurpose, source_code with def transform(arguments): returning a value, input_schema, output_schema, declared_reach: []. Draft until published. tool_id on the response is the id a flow step or node attaches; in a flow the value it returned is read as $ref:<step_id>.result.<key>.
Attaching toolsget_draft, add_node, update_node, set_root, edit_draft, publish_versiontool_ids on a node; global_tool_ids, pre_hook_tool_ids, post_hook_tool_ids on the agent draft. A pre hook's fixed_parameters may name what the conversation opened with: "$ref:conversation.number" is the caller's number on an inbound call and the contact's on an outbound one (update_tool, whose fixed_parameters replaces the stored set whole: send the values you are keeping along with the one you are adding). A publish is refused unless the graph has a root node (set_root) and the draft names an ai_profile_id. Nothing takes traffic until the version is published.

Where next

  • Recipes show the operation sequences for the common jobs, each one run as a test against the API before it is published.
  • Tools and their parameters — how to read what a built-in accepts, what a fixed parameter does, and why a fixed_parameters edit has to send the whole set.
  • The API Reference is complete and generated from the running app.
  • For agents explains the MCP server and the machine-readable copies of these pages.
Last modified on November 4, 2026
How to write an agent
On this page
  • The model
  • The pieces
  • The ladder
  • One versus the other
  • What never goes inside a tool
  • A worked example
  • One key per person
    • A grant per roster entry
  • The words the API uses
  • Where next