Skip to main content
When a tool needs a static secret from the user — an API key, a personal access token, or a username/password pair — declare an authentication config with authType: "user_credentials" on the tool. IMP renders a secure credential form directly in the chat UI the first time the tool is called, stores the values encrypted at rest, and injects them into every subsequent execute call. Your code never receives an unentered form; it only runs when credentials are present.

Prerequisites

What IMP handles for you

When authentication is declared with authType: "user_credentials":
  1. Credential form prompt — On the first call (and whenever stored credentials have been revoked or invalidated), IMP skips execute and returns a structured credentialRequest payload. The chat interface renders this as a typed form card — password fields are masked, help text is shown below each input.
  2. Secure submission — When the user submits the form, the frontend posts { nonce, values } to POST /credentials/submit. The endpoint requires a valid session and cross-checks the session user against the nonce (15-minute TTL, AES-encrypted) to prevent replay attacks across users.
  3. Encrypted storage — Values are AES-encrypted (keyed off NEXTAUTH_SECRET) and stored in the user_credentials table under (provider, userId). Plaintext never persists on disk.
  4. Injection — On every subsequent tool call, IMP decrypts the stored row and injects credentials into inputs before calling execute. Values transit the server only — they never reach the model.
  5. History scrubbing — The credentialRequest payload (which contains the nonce) is replaced in the model-visible conversation history with a scrub placeholder. A system guardrail also instructs the model to never ask the user to type credentials into the chat.
Your code only needs to declare the fields, optionally validate them on submission, and use inputs.credentials in execute.

What you will build

An ERP integration tool that authenticates with an API key. The key is collected once through a secure in-chat form and reused for every subsequent call.
1

Declare the tool with user credentials authentication

src/tools/erp-orders.ts
The credentials object injected into inputs is typed as Record<string, string> — the same shape the user submitted through the form, one key per fields[].name:
2

Understand the credential form flow

When the agent calls erp_orders for a user who has not yet entered credentials:
  1. IMP checks the credential store for a (example_erp, userId) row. None exists.
  2. IMP skips execute and returns a structured short-circuit result:
  1. The chat interface renders a credential form card. Password fields are masked; help text appears below each input.
  2. The user enters their API key and username and clicks Save. The frontend posts { nonce, values } to POST /credentials/submit using the current session cookie.
  3. The submit endpoint verifies the session, decrypts the nonce (rejects if expired or tampered), cross-checks the session user against the nonce’s userId, runs the optional validate hook, then writes the AES-encrypted values to the store. Returns { ok: true }.
  4. The agent is notified that the form was submitted and calls erp_orders again.
  5. IMP finds the stored row, decrypts it, and injects credentials into execute.
You do not need to handle this fallback in your code — IMP manages it entirely.
3

Share credentials across tools

Multiple tools that access the same provider (for example, an erp_invoices tool alongside erp_orders) should share the same provider key. The user fills in the form once; all tools in the group reuse the same stored row.
src/tools/erp-invoices.ts
When tools share a provider, they share one stored credential set per user. The fields arrays on all tools in the group must be identical (same names, same count). A mismatch causes a field-set validation error at the submit endpoint.
4

Self-healing with CredentialInvalidError

If the upstream API rejects the stored key (for example, the user rotated their API key in the ERP console), throw CredentialInvalidError inside execute. IMP catches it, deletes the stale stored row, and returns a fresh credential form in the same turn — no extra code required.
CredentialInvalidError takes two arguments: the provider string (must match authentication.provider) and an optional human-readable reason. The reason is included in the error message but is not shown directly to the user.Only throw CredentialInvalidError for the tool’s own provider. Any other error is re-thrown and surfaces as a normal tool error.
5

Register on the app

src/exulu.ts
Enable the tools in the agent workbench. No additional server configuration is required — NEXTAUTH_SECRET (already set for your IMP installation) is used as the encryption key. Make sure BACKEND is set to the backend’s public base URL so IMP can build the submitUrl in the credential form.

Security posture

User management

Users can view and revoke their saved credentials in Settings → Connections. The interface lists every (provider, authType) pair the user has stored, with a Revoke button for each. After revocation, the next tool call that needs those credentials shows the credential form again. The same operations are available via the API (session-authenticated):

What you built

  • An ERP orders tool with ExuluUserCredentialsConfig that collects an API key and username through a secure in-chat form.
  • A validate hook that tests the credentials against the upstream API before storing them.
  • A CredentialInvalidError throw that triggers automatic re-prompting when the stored key is rejected.
  • A shared provider key so multiple ERP tools reuse the same credential set.

Next steps

ExuluTool — configuration

Full ExuluUserCredentialsConfig reference — every field, CredentialField type, and the injected credentials shape.

OAuth tools

Use the authorization-code + PKCE flow when the provider supports OAuth instead of static keys.

Custom tools

Build tools without authentication using Zod schemas and admin config params.

Tool approvals

How users see and approve tool calls in the chat interface.