# Custom API Tools

> Give your Mojeeb agent tools that call your own system through the Public API — add a tool as one link with what the AI fills in, test it, switch it on and read its runs.

A custom API tool lets the agent's AI call your own system in the middle of a conversation: check an order, look up stock, confirm a booking. It's the dashboard's [Custom API](/docs/tools/custom-api) tool through the API, so an integration or an AI assistant can set it up for you. Everything lives under `/v1/agents/{agentId}/custom-api-tools`. Custom API is on paid plans; on the free plan every call here answers `403 feature_not_available`.

## How does a tool work?

A tool is one request your system answers, written as a link with a `{{name}}` placeholder for each thing the AI fills in, plus `when_to_use`, which tells the AI when to call it. When a customer asks something the tool answers, the AI fills in the inputs from the conversation, Mojeeb calls your link, and the AI replies from your system's answer. The AI only uses a tool that is switched on, and a tool can only be switched on after a passing test.

## How do I add a tool?

`POST` the tool. It starts switched off.

```bash
curl -X POST https://api.mojeeb.app/v1/agents/YOUR_AGENT_ID/custom-api-tools \
  -H "Authorization: Bearer mk_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Order status",
    "when_to_use": "When the customer asks where their order is.",
    "method": "GET",
    "url": "https://api.yourshop.com/orders/{{order_id}}",
    "inputs": [{ "name": "order_id", "description": "The order number the customer gives", "required": true }],
    "login": { "type": "api_key", "location": "header", "name": "X-API-Key", "api_key": "YOUR_SHOP_KEY" }
  }'
```

Every field, the login types and the limits are in the [Add a custom API tool reference](../api-reference/create-custom-api-tool). Write `when_to_use` the way you'd brief a colleague: the AI reads it to decide when to call the tool. Everything is checked before anything is saved: a broken rule answers `422 invalid_request`, with the field in `param` and a sentence saying what to change, and changes nothing.

## How do I test it and switch it on?

Test it with a sample value for each input:

```bash
curl -X POST https://api.mojeeb.app/v1/agents/YOUR_AGENT_ID/custom-api-tools/TOOL_ID/test \
  -H "Authorization: Bearer mk_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{ "inputs": { "order_id": "1042" } }'
```

A test is one real call to your system, without the AI (two for a write on a site that needs a CSRF token). `passed` says how it went and `response` shows what your system answered, shortened to the length the [Test a custom API tool reference](../api-reference/test-custom-api-tool) gives (the AI reads more of it), so you can check it holds what the AI needs. Once a test passes, switch the tool on with a `PATCH` that sends only `"active": true`. Any later change to the request (the method, link, headers, body format, body or inputs) needs a new passing test before the tool can be switched on again; a tool that's already on stays on.

## How does the login work?

The login belongs to the site, which the dashboard calls the domain: every tool whose link starts with a login's address, such as `https://api.yourshop.com`, uses that login.

- **A new site** gets the `login` you send with its first tool, or none when you send none.
- **A site you already have a login for** uses it: leave `login` out.
- **Changing a login other tools use** changes it for all of them, so it also needs `"change_shared_login": true`. Without it the answer is `409 login_shared`; sending a secret counts as a change.
- **A key limited to some agents** can add a tool to a login, or change it, only if it can act on every agent whose tools use it, or the answer is `403 shared_with_other_agents`.

A `login` you send replaces the saved one's settings as a whole, so send every setting you want kept. The key, token, password or client secret is write-only: left out, the stored one stays, but it never goes somewhere new. Moving a login to another site, or an OAuth login to another token URL, client or `client_auth`, needs the secret sent again. Reading a tool shows the login's type and settings, whether a secret is stored (`has_secret`) and how many tools use it (`used_by_tools`), never the secret. Put keys in `login`, not in `headers` or the body, so they stay encrypted and never come back.

## How do I see how a tool is doing?

List its runs: the times the AI used it, newest first, with how each went, how long it took and the conversation. Never what was sent or received.

```bash
curl https://api.mojeeb.app/v1/agents/YOUR_AGENT_ID/custom-api-tools/TOOL_ID/runs \
  -H "Authorization: Bearer mk_live_YOUR_KEY_HERE"
```

The tool's `login.status` reads `requires_reauth` when Mojeeb couldn't use the login (a secret is missing, or your token URL refused the client), and `error` when your system couldn't be reached. Your system answering a call with an error, such as `401`, shows in the runs, not in the status. See the [List a custom API tool's runs reference](../api-reference/list-custom-api-tool-runs).

## Common questions

### Can a placeholder go in the site part of the link?

No. The site decides the login, so only the path and the query can hold placeholders. For the same reason a tool can't move to another site: create a new tool for it.

### How many tests can I run?

Your company can run 30 tests a minute, across its keys and the dashboard. Beyond that the answer is `429 test_rate_limited`; retry after its `Retry-After`. A test also counts toward your key's own request budget; see [Rate Limits](../rate-limits).

### Does Mojeeb check who is asking before it calls my system?

No. Mojeeb sends the request with your login as it is, so your system decides who sees what. If a tool looks things up by phone or order number, have your system check the request makes sense rather than relying on the AI.

### Which links can a tool call?

Only https links to public sites, never a private or internal address. Mojeeb checks the address again on every call.

### Are these the same tools as in the dashboard?

Yes. A tool added through the API shows on the agent's Tools page in the dashboard, and a tool built in the dashboard can be read and changed through the API. A dashboard tool can also send fixed values: they aren't listed in `inputs`, and sending `inputs` leaves them as they are.

### Does it work with a system that needs a CSRF token, such as SAP?

Yes, once it's switched on for the site's login: turn on **Server needs a CSRF token before writes (SAP, OData)** in the dashboard, under **Security** → **Advanced** on any tool of that site; see [Custom API](/docs/tools/custom-api#what-if-my-server-needs-a-csrf-token). Every POST, PUT, PATCH or DELETE tool on the site then fetches a token first, tools you add through the API included. The API can't switch it on or show it yet, and sending a `login` leaves it as it is. The token request carries the header and query values typed into the tool as they are, such as `sap-client=100`, never anything written as a `{{placeholder}}`, which includes a dashboard tool's fixed values. When the token can't be fetched, the test's `error` starts with `csrf:`; see the [Test a custom API tool reference](../api-reference/test-custom-api-tool).

### Which plan do I need?

Custom API and the API key these calls need both come with every paid plan; see [Authentication](../authentication). On a plan without Custom API every call here answers `403 feature_not_available`. See [Plans and Pricing](/docs/billing/plans-and-pricing).

### Which scopes do these calls need?

`custom_api:read` to list and read tools and their runs, and `custom_api:write` to add, change, test, switch and delete them. Every key holds both today; see [Authentication](../authentication).
