Skip to content

Write a DADL File

You don’t have to write DADL files by hand. The fastest path is asking any LLM that knows the DADL spec:

“Create a DADL for the Stripe API — list customers, create charges, and manage subscriptions.”

Claude, GPT, Gemini — any model generates a working .dadl file in seconds. Drop it into dadl/, add a backend entry, done.

If you use Claude Code with ToolMesh connected, it can create the .dadl file, add the backend entry to config/backends.yaml, and set the credential — all in one session.

The guide below is for when you want to write or edit a DADL by hand — or understand what the LLM generated.


  • A running ToolMesh instance
  • Access to the REST API you want to describe
  • API credentials (token, key, or OAuth client)

Every DADL file begins with a spec reference and a backend block:

spec: "https://dadl.ai/spec/dadl-spec-v0.1.md"
backend:
name: my-api
type: rest
version: "1.0"
description: "My API — what it does in one line"

The name must be unique across your ToolMesh deployment. It becomes the prefix for all tools (e.g. my-api.list_items). The version is optional but recommended — ToolMesh uses it to detect when a newer DADL is available in the registry.

Add an auth block that tells ToolMesh how to inject credentials into requests:

auth:
type: bearer
credential: my_api_token
inject_into: header
header_name: Authorization
prefix: "Bearer "

Common auth patterns:

Typeinject_intoKey field
Bearer tokenheaderheader_name: Authorization
API key in headerheaderheader_name: X-API-Key
API key in queryqueryquery_param: api_key

The credential value references a key in ToolMesh’s credential store — the actual secret is never in the DADL file.

Define headers, pagination, and error handling that apply to all tools:

defaults:
headers:
Accept: application/json
Content-Type: application/json
pagination: &default-pagination
strategy: page
request:
page_param: page
limit_param: per_page
limit_default: 30
behavior: manual
max_pages: 10
errors: &standard-errors
format: json
message_path: "$.message"
retry_on: [429, 502, 503]
retry_strategy:
max_retries: 3
backoff: exponential
initial_delay: 1s

Use YAML anchors (&default-pagination) so tools can reference these with *default-pagination.

Each tool maps to one API endpoint:

tools:
list_items:
method: GET
path: /items
access: read
description: "List all items, optionally filtered by status"
params:
status:
type: string
in: query
description: "Filter by status"
enum: [active, archived]
page:
type: integer
in: query
pagination: *default-pagination
get_item:
method: GET
path: /items/{id}
access: read
description: "Get a single item by ID"
params:
id:
type: string
in: path
required: true
create_item:
method: POST
path: /items
access: write
description: "Create a new item"
params:
name:
type: string
in: body
required: true
description:
type: string
in: body
delete_item:
method: DELETE
path: /items/{id}
access: dangerous
description: "Permanently delete an item"
params:
id:
type: string
in: path
required: true

Key rules:

  • Path parameters use {param} syntax and must have in: path + required: true
  • Query parameters use in: query
  • Body parameters use in: body and are sent as JSON
  • access classifies the tool for policy/authorization mapping (see below)
  • Descriptions are what the AI agent sees — make them specific and actionable

The setup block tells users how to get API credentials:

setup:
credential_steps:
- "Go to https://example.com/settings/api"
- "Click 'Create API Key'"
- "Copy the generated key"
env_var: CREDENTIAL_MY_API_TOKEN
backends_yaml: |
- name: my-api
transport: rest
dadl: /app/dadl/my-api.dadl
url: "https://api.example.com"
docs_url: "https://docs.example.com/authentication"

Helps users understand what the DADL file covers:

coverage:
endpoints: 12
total_endpoints: 45
percentage: 27
focus: "items, users, search"
missing: "billing, webhooks, admin"
last_reviewed: "2026-03-29"

The optional access field on each tool enables role-based authorization. It tells policy files and OpenFGA what each tool does — without hard-coding tool names in policies.

Well-known values:

ValueWhen to use
readRead-only operations — listing, searching, fetching details
writeCreating or updating resources
adminPrivileged operations — permissions, settings, configuration
dangerousDestructive or irreversible operations — deleting resources

You can also use custom values for domain-specific needs (e.g. billing, pii, ops). ToolMesh passes them through unchanged.

tools:
list_users:
method: GET
path: /users
access: read
description: "List all users"
export_user_data:
method: GET
path: /users/{id}/export
access: pii
description: "Export all personal data for a user"

Best practice: Always set access explicitly. Don’t rely on HTTP method as a proxy — a POST /search is read, not write. A GET /admin/reset-cache is admin, not read.

spec: "https://dadl.ai/spec/dadl-spec-v0.1.md"
backend:
name: my-api
type: rest
version: "1.0"
description: "My API — items and users management"
auth:
type: bearer
credential: my_api_token
inject_into: header
header_name: Authorization
prefix: "Bearer "
defaults:
headers:
Accept: application/json
errors:
format: json
message_path: "$.message"
retry_on: [429, 502, 503]
coverage:
endpoints: 3
total_endpoints: 45
percentage: 7
focus: "items"
last_reviewed: "2026-03-29"
setup:
credential_steps:
- "Go to https://example.com/settings/api"
- "Create an API key with read/write scope"
env_var: CREDENTIAL_MY_API_TOKEN
backends_yaml: |
- name: my-api
transport: rest
dadl: /app/dadl/my-api.dadl
url: "https://api.example.com"
tools:
list_items:
method: GET
path: /items
access: read
description: "List all items"
get_item:
method: GET
path: /items/{id}
access: read
description: "Get item by ID"
params:
id:
type: string
in: path
required: true
create_item:
method: POST
path: /items
access: write
description: "Create a new item"
params:
name:
type: string
in: body
required: true