Functions
A function is server-side logic stored in ekoDB. It is parameterized, versioned and composable, and one function can combine queries, writes, full-text and vector search, key-value operations, AI calls and outbound HTTP requests in a single call. You define it once as JSON, and it runs next to your data.
The same function is your app's API and your agents' tool:
- Apps call it over the REST API or any client library.
- Agents find it by name and run it as a tool.
Both callers run the same stored definition, and the same permission checks apply to both.
How it fits together
Every caller reaches the same definition. Change the function and every caller gets the new behavior on its next call, with no app release and no agent prompt change.
Walkthrough: one function, an app and an agent
This walkthrough uses an items collection shaped like the one in Build Your First AI Agent:
{ "name": "USB-C cable", "category": "cables", "quantity": 1, "restock_threshold": 5 }
Step 1: Define the function
Creating, updating and deleting functions are admin operations, so use an admin key. The function below returns items whose quantity is below a threshold the caller passes in.
- Direct API
- Client Libraries
curl -X POST https://{EKODB_API_URL}/api/functions \
-H "Authorization: Bearer {ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"label": "low_stock_items",
"name": "Low stock items",
"description": "Items whose quantity is below max_quantity",
"version": "1.0.0",
"parameters": {
"max_quantity": {
"required": false,
"default": 5,
"description": "Return items with a quantity below this number"
}
},
"functions": [
{
"type": "Query",
"collection": "items",
"filter": {
"type": "Condition",
"content": {
"field": "quantity",
"operator": "Lt",
"value": "{{max_quantity}}"
}
},
"limit": 50
}
],
"tags": ["inventory"]
}'
- 📘 TypeScript
- 🐍 Python
import { EkoDBClient } from "@ekodb/ekodb-client";
const client = new EkoDBClient({
baseURL: process.env.EKODB_URL,
apiKey: process.env.EKODB_ADMIN_API_KEY,
});
await client.init();
await client.saveUserFunction({
label: "low_stock_items",
name: "Low stock items",
description: "Items whose quantity is below max_quantity",
version: "1.0.0",
parameters: {
max_quantity: {
required: false,
default: 5,
description: "Return items with a quantity below this number",
},
},
functions: [
{
type: "Query",
collection: "items",
filter: {
type: "Condition",
content: { field: "quantity", operator: "Lt", value: "{{max_quantity}}" },
},
limit: 50,
},
],
tags: ["inventory"],
});
import os
from ekodb_client import Client
client = Client.new(os.environ["EKODB_URL"], os.environ["EKODB_ADMIN_API_KEY"])
await client.save_function({
"label": "low_stock_items",
"name": "Low stock items",
"description": "Items whose quantity is below max_quantity",
"version": "1.0.0",
"parameters": {
"max_quantity": {
"required": False,
"default": 5,
"description": "Return items with a quantity below this number",
},
},
"functions": [
{
"type": "Query",
"collection": "items",
"filter": {
"type": "Condition",
"content": {
"field": "quantity",
"operator": "Lt",
"value": "{{max_quantity}}",
},
},
"limit": 50,
},
],
"tags": ["inventory"],
})
POST /api/functions creates a function and returns 409 Conflict if the label already exists. Update an existing one with PUT /api/functions/{id_or_label}. The description on the function and on each parameter is what an agent reads when it decides whether to use the function, so write it for that reader.
Step 2: Call it from your app
Apps call a function with POST /api/functions/{label_or_id} and a JSON body of parameters. Exchange your key for a token first, and use that token wherever {TOKEN} appears below. Every client library wraps this as callFunction / call_function.
- Direct API
- Client Libraries
curl -X POST https://{EKODB_API_URL}/api/functions/low_stock_items \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{ "max_quantity": 5 }'
- 📘 TypeScript
- 🐍 Python
const result = await client.callFunction("low_stock_items", {
max_quantity: 5,
});
for (const item of result.records) {
console.log(item);
}
result = await client.call_function("low_stock_items", {"max_quantity": 5})
for item in result["records"]:
print(item)
A call returns { "records": [...], "stats": {...} }. Record values follow the same use_typed_values setting as find and query, so a client configured for typed values gets the same shape from a function as from a read. Omitted optional parameters take their defaults, a missing required parameter is rejected, and an unknown parameter name is rejected.
The function is now an endpoint for your app. Rust, Go and Kotlin call it the same way; see Client Libraries: Functions.
Step 3: Have an agent call the same function
Agents do not need one tool per function. They use three built-in tools:
| Tool | What the agent does with it |
|---|---|
list_functions | Finds the functions it can call, with their labels, names, descriptions and parameter names |
get_function | Reads one function's full definition, including its steps and parameters |
call_function | Runs a function by label, with { "label": "...", "params": { ... } } |
Open a chat session with tools turned on, scoped to the items collection and to the function tools, then ask a question the function answers:
# 1. Create a chat session with the function tools enabled
SESSION=$(curl -s -X POST https://{EKODB_API_URL}/api/chat \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"collections": [{
"collection_name": "items",
"fields": [{"field_name": "name"}, {"field_name": "category"}]
}],
"llm_provider": "openai",
"llm_model": "gpt-4",
"system_prompt": "You manage inventory. Prefer stored functions over raw queries.",
"tool_config": {
"enabled": true,
"allowed_tools": ["list_functions", "get_function", "call_function"]
}
}' | jq -r '.chat_id')
# 2. Ask a question the function answers
curl -X POST https://{EKODB_API_URL}/api/chat/$SESSION/messages \
-H "Authorization: Bearer {TOKEN}" \
-H "Content-Type: application/json" \
-d '{ "message": "Which items have fewer than 3 in stock?" }'
The agent calls list_functions, sees low_stock_items and its max_quantity parameter, and runs it:
{
"label": "low_stock_items",
"params": { "max_quantity": 3 }
}
It answers from the records the function returns, and the response lists each tool call in tool_call_history. An AI Agent deployment uses the same three tools, so an agent you deploy from the dashboard can find and run low_stock_items with no extra setup.
An agent whose key has admin privileges and whose session allows write operations can also create, update and delete functions and create schedules for them. A session's write setting alone does not bypass the admin-only checks on those operations. Whichever way a function was made, every caller runs that same definition.
Permissions
The same rules apply whether an app or an agent makes the call.
- Defining needs an admin key. Creating, updating and deleting functions, and creating schedules for them, are admin operations.
- Calling needs a valid token. Any valid token for the instance can call any function, by id or label (
POST /api/functions/{label_or_id}) or through a path route. Per-function call grants are on the roadmap. - A function's steps run with the server's own authority. The calling key's collection grants and field restrictions are not applied to the steps, so a function can read and write collections the key could not reach directly. Treat each function as a privileged operation: review it as one, and keep it to the single operation you want to expose.
- An agent's
call_functionis limited by its session. A chat session runs a function the same way, and the session's collection scope and tool configuration decide what it may run: a function that reaches a collection outside that scope, through a nested stage or a called function, is refused. - Writes made by a function are not yet audited. The audit trail covers direct writes; a function's own writes add no entry yet. Running a function as its caller or its author, and auditing its writes under that identity, are on the roadmap.
For an agent your own users talk to, give it stored functions instead of raw read and write tools. Each function is a reviewed, versioned operation with declared parameters, and the agent can do only what those functions allow.
Triggers
Apps and agents are not the only callers. A function can also run on a timer, on an inbound event, or behind a URL of your choosing.
| Trigger | How it calls the function | Runs as |
|---|---|---|
| Schedule | A cron schedule (POST /api/schedules, six-field cron with a timezone) runs the function with stored parameters. See Scheduled Functions. | The schedule's recorded author |
| Webhook | External services such as Stripe or GitHub POST signed events to /api/webhook/{name}. ekoDB verifies the HMAC-SHA256 signature, then calls the receiver's function with the JSON body as parameters. A missing or wrong signature returns 401. | The receiver's recorded author |
| Custom route | Give a function an http_method and an http_path such as /users/:id, and it answers at /api/route/users/{id} with path placeholders passed as parameters. These routes use the same authentication as POST /api/functions/{label}. | The authenticated caller, as above |
A webhook receiver names the function to call, the shared secret and the signature header (X-Hub-Signature-256 by default). Agents manage receivers with the webhook_receiver tool.
What a function can do
A function is an ordered list of steps whose results flow from one to the next. The step types cover:
- Reads and search:
Query,FindById,FindOne,TextSearch,VectorSearch,HybridSearch - Writes:
Insert,BatchInsert,Update,UpdateById,Upsert,Deleteand more, optionally as one transaction - Shaping:
Group,AddFields,Project,Count - Control flow:
If,ForEach,Parallel,TryCatch,Return,Validate, andCallFunctionto compose functions - Key-value and caching: KV reads and writes, and stale-while-revalidate caching
- AI:
Chatcompletions andEmbedfor embeddings - Auth and crypto: password hashing, JWT sign and verify, HMAC, AES-GCM and TOTP
- Outbound HTTP: call any REST API from inside the function
The full catalog, parameter rules, result chaining and transaction semantics are in Functions Architecture.
Next steps
- Functions Architecture: every step type, parameters, composition and versioning
- Scheduled Functions: run functions on a cron schedule
- AI Agents: deploy an agent that calls your functions
- Client Libraries: Functions: manage and call functions from every SDK