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

Tools and their parameters

A tool is a configured, named instance of something the platform offers: a built-in, a tool imported from an MCP server, a flow, or custom code. This page is about the built-ins and the parameters they accept, because two facts about them surprise everyone who meets them through the API for the first time:

  • An instance's parameters are not a copy taken when it was created. They are the deployment's current ones. A parameter the platform added last week is accepted by an instance created last year, and nothing is re-created or refreshed to get it.
  • fixed_parameters on a PATCH replaces the whole set. It does not merge. Sending the one key you want to add silently unfixes the rest.

The calendar built-ins are the running example throughout.

Discovering the built-ins

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

One entry per built-in this deployment runs. The answer describes the deployment, not your rows: it is the same for every organization, less any built-in that needs a feature your key does not carry. This is the read to make before creating anything, and the only thing that can say what a built-in accepts today.

One entry, in full:

Code
{ "name": "calendar_create_event", "input_schema": { "type": "object", "properties": { "start": {"type": "string", "description": "When the event starts."}, "end": {"type": "string", "description": "When the event ends."}, "summary": {"type": "string", "description": "The event's title."}, "description": {"type": "string", "description": "Free text on the event."}, "location": { "type": "string", "description": "Where the event is: an address, or coordinates as 'lat,lng'. Kept exactly as given." }, "attendees": { "type": "array", "items": {"type": "string"}, "description": "Email addresses to invite, exactly as the caller gave them." }, "refuse_overlap": { "type": "boolean", "description": "Optional: true refuses a slot that overlaps anything already in the calendar, naming what it collides with; false (the default) writes the slot as asked." }, "calendar_id": { "type": "string", "description": "The calendar within the connected account. Fixed on the instance." }, "timezone": { "type": "string", "description": "The IANA timezone busy ranges are expressed in and naive instants are read in, e.g. Europe/Madrid. Fixed on the instance." } }, "required": ["start", "end", "summary", "calendar_id", "timezone"], "additionalProperties": false }, "fixed_parameters": ["calendar_id", "timezone"], "provider": "google", "required_flag": null }
FieldWhat it is
nameThe built-in's name, and what an instance of it carries in config.builtin when you create it.
input_schemaEvery parameter the built-in accepts today, the keys a person fixes included. This is the schema the instance has, not a template to copy and edit.
fixed_parametersA list of key names, not values: the parameters a person is expected to fill on the instance, because they decide what the instance is rather than what one call does. Here, which calendar and which timezone.
providerThe connection provider an instance needs — google for the calendars — or null for a built-in that needs no authorization of yours.
required_flagThe feature an organization must have to make an instance of it, or null. Entries naming a feature your key does not carry are absent from the answer altogether, so what you read here is what you may create.

An entry with input_schema: null is a built-in no client creates by hand — the three a collection brings with it (custom_data_query, custom_data_get, custom_data_write, which appear in list_tools as query_<slug>, get_<slug> and write_<slug> the moment the collection exists). It is listed because the deployment runs it, and there is nothing to configure on it.

The five calendar built-ins

All five fix calendar_id and timezone, and all five point at a google connection. Which of them that connection backs depends on what the person authorized: see_busy backs the busy read and nothing else, see_appointments backs the listing and no write, manage_events backs the listing and every write. A client who wants both reads grants both — the busy read is its own permission at Google too.

Built-inWhat it doesParameters besides the fixed two
calendar_read_busyThe busy ranges between two instants, and nothing computed on top: no free slots, no "next available".time_min and time_max together, or days alone (1–31) from the moment of the call; never both forms.
calendar_list_eventsEach event between two instants as the diary holds it — start, end, title, location, its identifier, whether the platform created it — the person's own entries included.The same interval as the busy read.
calendar_create_eventWrites the event it was given. It does not check that the slot fits unless refuse_overlap says so.start, end, summary (required), description, location, attendees, refuse_overlap.
calendar_update_eventReplaces what it is given and leaves the rest as it was: a name learnt after booking, a visit moved, a second person invited.event_id (required), then any of start, end, summary, description, location, attendees, refuse_overlap. Moving an event gives both start and end; a start alone would leave the length to a guess.
calendar_cancel_eventRemoves the event, and is its own action — so an agent can be allowed to change visits without being allowed to cancel them.event_id.

An event is changed or cancelled only if the platform created it in that same calendar; one a person put there answers exactly as one that does not exist. The identifier is the one calendar_create_event answered with.

refuse_overlap: whether a write checks the slot first

A create or an update can refuse a slot that overlaps anything already in the diary, and the refusal names the ranges it collides with, so the model can offer another time instead of booking on top of a visit.

It is off unless it is turned on, because some clients overlap on purpose. It is a parameter of the two write tools like any other — not a setting of its own — so a client turns it on by fixing refuse_overlap: true on the instance; left unfixed, it is the model's to pass or not. It is a read just before the write and not a lock, so two writes at the same instant may still both land.

location: kept exactly as it was given

Where the event is, as an address or as coordinates written lat,lng. It is stored as given and read back the same way: nothing is geocoded, reformatted or looked up. calendar_list_events returns it as part of each event, and an update replaces it.

Creating an instance

TerminalCode
curl -X POST https://YOUR_PLATFORM_ORIGIN/api/v1/tools \ -H "Authorization: Bearer $ATTEQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "book_visit", "description": "Put a visit in the diary.", "source": "builtin", "config": {"builtin": "calendar_create_event"}, "fixed_parameters": {"calendar_id": "primary", "timezone": "Europe/Madrid"}, "connection_id": "8f1c0e6a-5c2e-4f1e-9c2a-0b7d5a3e1f44" }'

Three things about that body:

  • config.builtin is what makes it an instance of this built-in, exactly as config.flow_id is what makes a tool a flow. Spell the name as list_builtins gave it; a name this deployment does not run reaches nothing.
  • No input_schema. What a built-in accepts is the platform's to decide, so there is nothing to declare: the instance has the deployment's schema whatever you send.
  • connection_id and never a credential. The Google connection is authorized by the person whose diary it is, through Google's consent screen, and list_connections gives you its id; its capabilities say which of the five tools it backs. A tool's configuration that carried a key itself is refused when it is created or changed.

The answer is the instance. A tool's config is never returned — it is the place credentials can live — which is why builtin and connection_id are published as their own fields:

Code
{ "id": "3b9f2c14-7a6e-4f0b-9a83-2c0d61e4a7bd", "org_id": "0a6c1f33-82d4-4b55-8e01-7d9f4c2b6a10", "name": "book_visit", "description": "Put a visit in the diary.", "source": "builtin", "builtin": "calendar_create_event", "fixed_parameters": {"calendar_id": "primary", "timezone": "Europe/Madrid"}, "deadline_seconds": 30, "conversation_only": false, "requires_confirmation": false, "connection_id": "8f1c0e6a-5c2e-4f1e-9c2a-0b7d5a3e1f44", "connection_group": null, "mcp_server": null, "owner": null, "holds_credential": false }

The answer carries one more field, elided above for length: input_schema, the built-in's current schema — the same object the catalogue listed. Why it is the current one, and not the one that existed when the row was written, is the next section.

A flow as a tool

A flow is offered to an agent as one tool by an instance with source "builtin" and the flow's id in config.flow_id. Nothing else goes in config: no endpoint, no connection_id, no header. The platform runs the flow itself, so there is no address to call it at, and an HTTP tool pointed at this platform is not a way to run one.

TerminalCode
curl -X POST https://YOUR_PLATFORM_ORIGIN/api/v1/tools \ -H "Authorization: Bearer $ATTEQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "check_order", "description": "Look up the status of an order by its number.", "source": "builtin", "config": {"flow_id": "FLOW_ID"}, "input_schema": { "type": "object", "properties": {"order_number": {"type": "string"}}, "required": ["order_number"] } }'
  • input_schema is yours to write. It is what the agent fills in when it calls the flow, and the flow's steps read each value as $ref:trigger.<key> (here $ref:trigger.order_number). A flow that needs nothing from the conversation takes {"type": "object", "properties": {}}.
  • The answer's id is what an agent carries: in global_tool_ids on the draft (edit_draft) for every situation, or in a node's tool_ids (update_node) for one.
  • Testing the instance runs the flow, every step, with the parameters you send: POST /api/v1/tools/{id}/test with {"parameters": {"order_number": "A-1"}}.

An instance accepts what the built-in accepts today

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

A built-in instance's parameters are the deployment's current ones. What a built-in accepts is the platform's to decide and it changes when the platform does, so an instance created months ago accepts — and offers the model — what that built-in accepts now, less what you fixed on it. Nobody re-creates or refreshes an instance to receive a parameter the platform added. location and refuse_overlap were both added to the calendar writes after the first instances of those tools existed, and they work on every one of them.

Two consequences worth stating plainly, because both have been got wrong:

  • An instance is not evidence about what a built-in accepts. If the parameter you are looking for is not where you expected it on a tool row, read GET /api/v1/tools/builtins — the deployment's own answer — before concluding it does not exist.
  • A value fixed on a parameter the built-in no longer has stays on the instance and reaches nothing. It is not an error and it is not applied; it is a leftover to clean up.

fixed_parameters on the instance says which keys a person fixed, and the model is shown the schema less those keys: the instance always says which values are fixed on it.

Fixing a parameter

A parameter fixed on the instance by a person is absent from what the model is shown. The model does not know it exists, cannot ask for it and cannot overwrite it — which is the whole point for calendar_id and timezone: a model that could name the calendar could name the wrong one, and one that could pick the timezone would be an hour out on a schedule nobody could audit. The record of the conversation shows what each run actually ran with, fixed values included, so a fixed value is hidden from the model without being hidden from you.

Any declared parameter of any instance can be fixed or left to the model, one parameter at a time, from the instance's own schema. Turning the overlap check on is therefore not a special call — it is fixing refuse_overlap on the instance. Which brings us to the one trap on this page.

The trap: fixed_parameters replaces the whole set

PATCH /api/v1/tools/{tool_id} is a partial edit: only the fields you send change. But fixed_parameters is one field whose value is an object, and the object you send replaces the stored one whole. What your change does not name is no longer fixed and returns to the model.

So this call, which looks like "also fix the overlap check":

Code
{"fixed_parameters": {"refuse_overlap": true}}

is accepted, and it leaves the instance fixing refuse_overlap and nothing else. calendar_id and timezone are no longer fixed, so the model is now shown both and asked to name them itself — and since the schema requires them, the next call either guesses a calendar or fails outright.

The right call reads the instance, merges, and sends the whole set back:

TerminalCode
curl https://YOUR_PLATFORM_ORIGIN/api/v1/tools/$TOOL_ID \ -H "Authorization: Bearer $ATTEQ_API_KEY"
TerminalCode
curl -X PATCH https://YOUR_PLATFORM_ORIGIN/api/v1/tools/$TOOL_ID \ -H "Authorization: Bearer $ATTEQ_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "fixed_parameters": { "calendar_id": "primary", "timezone": "Europe/Madrid", "refuse_overlap": true } }'

Unfixing a parameter is the same call without it: there is no "remove this key" operation, because the set you send is the set that is fixed. Every other field of update_tool behaves as you would expect — a name alone changes the name and touches nothing else.

Read, merge, write, every time you touch fixed_parameters. Two people editing one instance an hour apart, each sending only their own key, leave it fixing whatever the second one sent.

If you are a model reading this

  1. Read the catalogue, then create. GET /api/v1/tools/builtins names every built-in, its current parameters, the keys a person fixes and the connection provider it needs. Guessing a parameter name or copying one out of an older instance is how an instance gets created that the runner then refuses.
  2. Do not send input_schema for a built-in. It is the platform's, not yours; send source: "builtin" and config.builtin and leave the schema alone.
  3. Never report that a parameter does not exist because one tool row does not mention it. The catalogue is the answer; a row is one client's configuration of it.
  4. Treat fixed_parameters as read-modify-write. A PATCH that names it replaces it.
  5. A tool's config is never returned. If you need to know which built-in an instance is, read builtin; for its authorization, connection_id or connection_group.

Where next

  • How it fits together — which pieces a job needs, and the words the API uses for each.
  • Book an appointment without double-booking — a booking built end to end, as the sequence of operations it is, run as a test against the API before it was published.
  • API Reference — list_builtins, create_tool, get_tool, update_tool and test_tool, with their schemas.
Last modified on November 4, 2026
ErrorsHTTP tools and connections
On this page
  • Discovering the built-ins
  • The five calendar built-ins
    • refuse_overlap: whether a write checks the slot first
    • location: kept exactly as it was given
  • Creating an instance
  • A flow as a tool
  • An instance accepts what the built-in accepts today
  • Fixing a parameter
  • The trap: fixed_parameters replaces the whole set
  • If you are a model reading this
  • Where next
JSON
JSON
JSON