External API Tools
Connect your own REST API with a key — the agent gets one approval-gated tool per credential
External API Tools
External API tools let you attach your own REST API to the agent — a Perplexity key, an internal billing service, any endpoint that takes a header, a bearer token, basic auth, or a query-string key. You store the API once; the agent then gets one tool for it that can issue requests with the method and path you ask for, with your host and credentials applied automatically.
External API tools are org-level only (#1253): an org owner defines each API, enables it per workspace, and can review what leaves the platform. The previous personal tier — credentials that followed their owner into every workspace with no org visibility — was removed for privacy.
External API tools live next to skills and agents in the trust model: the model only ever supplies method, path, query, body and extra headers. The host, the authentication and the configured custom headers are pinned to the stored credential and can never be overridden by the model.
Where credentials live
| Scope | Who manages it | Where | Reach |
|---|---|---|---|
| Organization | Org owners | Org console → API Tools (/org/<slug>/api-tools) | Only workspaces an owner explicitly enables it in |
Adding a credential
-
Open the org page and click New API credential.
-
Fill in:
Field Notes Name Shown to the agent; make it descriptive ("Perplexity", "Acme Billing") Slug Defines the tool name call_api_<slug>; cannot be changed laterHost Origin only — https://api.example.com(no path, query or fragment)Credential type One of Bearer token, Header, Basic auth, Query parameter, Multiple headers Secret The matching value(s) for the chosen type — write-only, encrypted at rest Custom headers Optional platform-added headers; values may use system variables (below) Notes Optional usage text the agent sees when deciding when to call the API -
Test the credential from the row menu — the platform makes a real request to the host with the auth (and custom headers) applied and reports the HTTP status and which header names were sent.
The secret is write-only in every direction: once saved, no API or UI will ever show it again. Editing any other field keeps the stored secret; rotating means entering a new one. Changing the credential type requires the secret for the new type in the same save.
Custom request headers with system variables
A credential can define up to 20 extra request headers that the platform adds
to every call the agent makes with it. Header values are literal text with
optional {VARIABLE} tokens; the platform fills the values in server-side from
the signed-in user's context at call time. The model can never supply or
override these headers.
Example:
| Header name | Header value | Sent as (for the signed-in user) |
|---|---|---|
x-user-id | {USER_ID} | x-user-id: denvy.shi@igsl-group.com |
x-tenant | {ORG_SLUG} | x-tenant: chatbotdemo |
x-trace | surfwise-{REQUEST_ID} | x-trace: surfwise-7f3c… |
The downstream API can then authorise, personalise or log per end user without a separate credential per user. The value input in the form has an insert variable picker and shows a live preview rendered with your own values, so you can see exactly what will be sent.
Available variables
| Variable | Resolves to | Example |
|---|---|---|
{USER_ID} | Login ID of the current user (their email) | denvy.shi@igsl-group.com |
{USER_EMAIL} | Email of the current user (same value, explicit name) | denvy.shi@igsl-group.com |
{USER_UUID} | Internal immutable user ID | 3f2a… |
{USER_NAME} | Display name; falls back to the email | Denvy Shi |
{USER_ROLE} | The user's role in the current workspace | Editor |
{WORKSPACE_ID} | Current workspace ID | 128 |
{WORKSPACE_NAME} | Current workspace name | Sales KB |
{ORG_ID} | Organization of the workspace | 7 |
{ORG_SLUG} | Organization slug | chatbotdemo |
{ORG_NAME} | Organization name | Chatbot Demo |
{CHAT_ID} | Current chat thread ID | 5521 |
{REQUEST_ID} | New UUID per API call, for tracing and idempotency | 7f3c… |
{TIMESTAMP} | Call time, ISO 8601 UTC | 2026-10-08T03:17:29Z |
{DATE} | Call date, UTC | 2026-10-08 |
Rules:
- Names are case-sensitive;
{USERID}is accepted as an alias of{USER_ID}. {{and}}escape literal braces.- An unknown variable name is rejected when you save
(
UNKNOWN_VARIABLE, naming the token). - A variable that is unavailable in the current context — e.g.
{ORG_SLUG}on a page without an organisation — causes that header to be omitted from the call, never sent half-filled. - Header names cannot collide with platform-reserved headers (
Host,Cookie, …), the credential's own auth header names, or each other (case-insensitive). A model-supplied header with a custom header's name is rejected before anything is sent. - Merge order per call: model-supplied headers → custom headers → auth headers (auth always wins).
- Header values travel as ASCII: printable ASCII passes through, a
literal
%is sent as%25, and non-ASCII text is percent-encoded as UTF-8 (石文→%E7%9F%B3%E6%96%87). Receivers must URL-decode the value to recover the original text — every value round-trips.
Custom header values are not secret — they are stored in plain text, returned by the API and visible to anyone who can manage the credential. Secrets belong in the credential type above. Also note the privacy implication: the variables you configure (user identity, organisation, workspace) are sent to the third-party host on every call.
Enabling a credential in workspaces
Org credentials do not reach any workspace by default. On the org page, open the row's Workspaces action and tick the workspaces that may use it — the checklist replaces the previous selection on save. A workspace only loads the tool when it appears in the list; a sibling workspace keeps working without it.
Using it in chat
Ask the agent for something that needs the API ("search the web with
perplexity for…"). The agent sees a connected-APIs summary and calls
call_api_<slug> with the method/path it needs. By default every call asks
for your approval showing the exact request — and the resolved custom
headers (what identity data will be attached for you) — before it goes out.
Per-call variables ({REQUEST_ID}, {TIMESTAMP}, {DATE}) appear on the
card as (generated at call time): they are minted when the approved call
actually goes out. Non-ASCII values are shown readable, with a
"(sent percent-encoded)" note for the wire form. You can then allow it once,
or manage standing rules.
Config changes (new credential, rotation, disable, header edit, checklist edit) are live on the next message — no restart.
Approvals and permissions
Credential tools plug into the existing permission settings:
- Settings → Tool Permissions lists each tool as External API with the workspace scope. Default is Ask; set Allow or Deny per tool as needed.
- Deny wins; Ask falls back to the approval dialog in chat.
Each call is audited (api_credential.created, .updated, .deleted,
.workspaces_set on the management side), and neither secrets nor custom
header values ever appear in audit records — only field names and header
names.
Security model
- The secret payload is encrypted at rest (Fernet, same mechanism as model provider keys) and never appears in any API response, log or audit row.
- Every call is pinned to the stored origin — the transport layer enforces it — so a prompt cannot redirect your key to another host.
- Custom header names join the reserved set for that credential: the model can neither override them nor spoof the identity they carry.
- Resolved header values are stripped of CR/LF and control characters, so a stored value can never inject extra headers. Resolved values are never logged.
- Model-supplied headers and query parameters that collide with the credential's own auth material are rejected before anything is sent.
- Private-network hosts are refused by default; operators can opt in with
ALLOW_PRIVATE_API_CREDENTIALSfor internal APIs. - Bodies are capped at 32 KiB; responses are truncated at 256 KiB; requests time out after 30 seconds. Failures come back to the agent as text.
Limits
- Five credential types in this version; OAuth flows are not supported — use a long-lived token, or a header/query key.
- One credential = one API. An API needing a different key per workspace needs a separate org credential enabled per workspace.
- Up to 20 custom headers per credential; each value is at most 1024 characters. Variables are supported in header values only — not in query parameters, the path or the body.