> ## Documentation Index
> Fetch the complete documentation index at: https://docs.danubeai.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Onboard an Internal Tool

> Register a service your company runs, give the right people access, and operate it with caps, retention and audit

This guide takes an internal system from "reachable by us" to "callable by our agents, governed". The reference for each piece is on the [Organizations](/organizations/overview), [Policies](/organizations/policies), [Audit Log](/organizations/audit-log) and [Data Plane](/organizations/data-plane) pages.

## Prerequisites

* An organization you **own or administer**. Members can use internal tools; only owners and admins register them.
* For a system that is only reachable inside your network, an owner on the **Team or Enterprise plan**: enrolling a [data-plane agent](/organizations/data-plane) is an entitlement of the organization owner's plan and answers `402` otherwise.
* Something to register: an OpenAPI 3.x document (a URL or the file), a remote MCP server, or a list of HTTP endpoints you can describe as a [connector](/connectors).

## 1. Choose reachability

| The system is                                                                                      | Register it as | What runs the call                                                                                                                                                                 |
| -------------------------------------------------------------------------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Reachable from the internet (a hosted service, a public endpoint behind an API key)                | `internal`     | Danube, with the credential injected at call time                                                                                                                                  |
| Reachable only inside your network (a private IP, an internal DNS name, a service behind your VPN) | `local_only`   | Your [data-plane agent](/organizations/data-plane), which receives the tool definition and parameters plus credential *references*, resolves the secret locally and makes the call |

Both are visible to every member of the organization and nobody else. `local_only` covers HTTP services and remote MCP servers (streamable HTTP or SSE); the agent connects to the server, discovers its tools and makes every call, and Danube never reaches it. A local (stdio) MCP server cannot be registered for an organization at all. See [MCP servers](/organizations/data-plane#mcp-servers) for the transports and the credential format.

A database or warehouse (Postgres, Redshift, MySQL, Snowflake, BigQuery, Databricks, ClickHouse, Trino) is registered as `local_only` too, with `source: database`: each read-only query template becomes a tool, the connection string stays behind an `env://` or `vault://` reference the agent resolves, and the agent image is built with the drivers you need. [Connect a Warehouse](/organizations/connect-a-warehouse) covers the reference forms, the read-only role per engine and the errors.

## 2. Register

<Steps>
  <Step title="Open Create Tools">
    Go to [Dashboard > Tools > Create Tools](https://danubeai.com/dashboard/tools/new). For a spec-less HTTP service use the [Connector Builder](https://danubeai.com/dashboard/tools/connector) instead; it has the same owner and visibility controls.
  </Step>

  <Step title="Set the owner and the visibility">
    **Owner**: the organization. **Visibility**: *Internal* or *Runs inside your network*.
  </Step>

  <Step title="Point it at the source">
    An OpenAPI URL, an uploaded OpenAPI document, or a remote MCP server URL. A document without a `servers` entry needs a base URL.
  </Step>

  <Step title="Preview import">
    The preview is a dry run: it parses everything, writes nothing and lists every tool it would create, each marked `create` or `update`, with any parse errors verbatim. Fix what it reports, then create.
  </Step>
</Steps>

The same registration over the API, owner or admin, with a session token or an API key:

```bash theme={null}
curl -X POST "https://api.danubeai.com/v1/organizations/ORG_ID/services" \
  -H "danube-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "source": "openapi_url",
    "openapi_url": "https://inventory.example.com/openapi.json",
    "name": "Inventory",
    "description": "Stock levels and reorders",
    "visibility": "internal",
    "dry_run": true
  }'
```

`source` is `openapi_url`, `openapi_spec` (the document itself, as an object or JSON / YAML text, with `spec_filename` if you have one), `mcp` (`{"mcp": {"server_url": "https://...", "transport": "http", "auth_type": "bearer"}}`; `name` is required) or `connector` (`{"connector": {...}}`, a [connector spec](/connectors)). Run it once with `"dry_run": true`, read the `tools` and `errors` it returns, then repeat without `dry_run`. The real registration answers with the service, `tool_count`, `tools_failed`, `sync` (`running` for an MCP server whose tools are being discovered in the background) and `warnings`. Limits: a document of at most 5,000,000 bytes and 500 operations.

The service now appears on the Tools page, on the tab named after your organization, with an **Internal** or **In your network** badge, and in every member's search results. `GET /v1/organizations/{org_id}/services` lists it for the API.

## 3. Give people access

With no grants, every member of the organization can use the new tools. To narrow that, go to [Policies](https://danubeai.com/dashboard/organization/policies):

* The **first grant flips the organization to deny-by-default**, for every internal tool at once, not only this one. Create the allows you need in the same sitting. The public catalog stays open to members whatever you allow; a deny on a catalog service or tool is the one grant that reaches it.
* Allow a **team** the whole service, then deny the organization the one or two tools that should stay out of reach; a deny always wins. Teams are created and staffed under **Teams** on the Organization page.
* Owners and admins keep access without a grant; only an explicit deny reaches them.
* A member who is not granted a tool does not see it in search or in the service's tool list, and a call to it answers `403 Your organization does not have access to this tool`. When an agent reports that message, the fix is a grant from an admin, not a retry.

## 4. Credentials

An internal service usually authenticates with one key the company holds. Store it once for the organization, and every member who has no credential of their own for the service uses it:

```bash theme={null}
curl -X PUT "https://api.danubeai.com/v1/organizations/ORG_ID/credentials/SERVICE_ID" \
  -H "danube-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"credentials": {"type": "api_key", "value": "sk-live-...", "header_name": "X-API-Key"}}'
```

Or open the service page, add the credential and choose the organization in the form. A member who stores a personal credential for the same service overrides the shared one for their own calls. OAuth connections are always personal. The shared credential is listed under **Shared credentials** on the Organization page with who stored it and when; its value is never shown again.

For a `local_only` service the value must not be a value at all: store a **reference** that the agent resolves inside your network, `env://VAR_NAME` for an environment variable on the agent's host or `vault://<mount>/<path>#<field>` for HashiCorp Vault. A credential held only as a value in Danube is never shipped to the agent; the call fails with `credential_reference_unresolved`.

### Choosing an authentication method

A [connector](/connectors#authentication-methods) names its method in the spec and takes the secret as a credential. Pick by what the system checks on the wire: a static key or token it issued you is `api_key`, `bearer` or `basic`; an AWS service, or anything fronted by API Gateway with IAM authorization, is `aws_sigv4` with the service's signing name (`s3`, `athena`, `redshift-data`, `lambda`, `execute-api`) and region; an API that expects a token from your identity provider, an Okta or Entra application, a Google service account, or an Auth0 tenant, is `oauth2_client_credentials` with the provider's token URL and the scope or audience it requires; a service that authenticates the client certificate on the TLS handshake is `mtls`, with the certificate and key pasted as PEM. The credential form asks for exactly the fields the method reads, and on the data plane each of them is an `env://` or `vault://` reference, the same as any other secret.

## 5. Verify

1. **Test run.** Open the service on the Tools page and run one read-only tool, or over the API `POST /v1/tools/call/{tool_id}` with `{"tool_input": {...}}` from a member's key. A `local_only` tool needs an agent online: the Data Plane page shows each agent's **Online** badge and version.
2. **Audit log.** Open [Audit Log](https://danubeai.com/dashboard/organization/audit) and filter by the tool. You should see the `service.create` row from the registration and one `tool.execute` row per test call, with the key that made it and a hash of the parameters. A `tool.denied` row with `org_policy:no_grant` means step 3 is missing an allow.
3. **A second member.** Have someone with the `member` role search for the tool. Seeing it confirms visibility and the grants; not seeing it, when the organization has grants, means they are not granted.

## 6. Operate

| Concern                       | Where                                                                                                                  | What it does                                                                                                                                                           |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Calls per minute, daily spend | Policies > Limits, or `rate_limit_per_minute` / `daily_spend_cap_cents` on `PATCH /v1/organizations/{org_id}/settings` | Caps every call made in the organization's context, on top of per-key caps. Over the cap: `429` with `Retry-After`, or `403`, both `usage_limit_reached`, both audited |
| Execution history             | Organization > Data handling, or `execution_retention_days`                                                            | Deletes the organization's execution rows older than the setting, nightly                                                                                              |
| Audit retention               | Audit Log, or `audit_retention_days`                                                                                   | Deletes audit events older than the setting (30 days minimum), nightly                                                                                                 |
| Data residency                | Organization > Data handling, or `dataplane.store_payloads: false`                                                     | Data-plane parameters and results are never written to Danube; only a size and a SHA-256 are kept                                                                      |
| Hosted AI                     | Organization > Data handling, or `llm_internal_tools: false`                                                           | Keeps the organization's internal tool definitions and results out of Danube Chat, hosted workflows and the workflow generator; the caller still sees the real result  |
| Organization keys             | API Keys, the organization's tab                                                                                       | Keys that belong to the organization rather than a person; admins can revoke any of them, and a departing member's are revoked on removal                              |
| Changes to the service        | The service page (owner, or an owner / admin of the organization)                                                      | Edit, refresh tools, deprecate or delete; every write is audited                                                                                                       |

## Troubleshooting

Registration errors, from `POST /v1/organizations/{org_id}/services` and the dashboard's preview:

| Status | Message                                                                                                   | Fix                                                                                                                                                                            |
| ------ | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `400`  | `openapi_url is required for source openapi_url` (and the same for `openapi_spec`, `mcp`, `connector`)    | Send the field the chosen `source` needs                                                                                                                                       |
| `400`  | `Only OpenAPI 3.x documents are supported (3.0.0 to 3.1.0)`                                               | Convert a Swagger 2.0 document first                                                                                                                                           |
| `400`  | `The document has no title; give the service a name`                                                      | Pass `name`                                                                                                                                                                    |
| `400`  | `The document has no servers entry, so none of its operations can be called; give the service a base URL` | Pass `api_base_url`                                                                                                                                                            |
| `400`  | `No tools could be extracted from the document`                                                           | The document declares no operations Danube can call                                                                                                                            |
| `400`  | `The document declares N operations; the limit is 500 tools per service`                                  | Split the document, or trim the operations you do not need                                                                                                                     |
| `400`  | `N of M tools failed to register; the service was not created`, with `tools_failed`                       | More than half the operations failed; each entry names the method, path and error. Fix the document. Under half fail and the service is kept with the failures listed          |
| `400`  | `name is required for an MCP service` / `Only remote MCP servers can be registered for an organization`   | Pass `name`; a local (stdio) MCP server cannot be registered for an organization                                                                                               |
| `400`  | `Invalid MCP server URL: a local_only server needs an http or https URL with a host`                      | A `local_only` MCP server is not probed, but its URL still has to be one the agent can dial                                                                                    |
| `400`  | `Cannot reach MCP server at <url>: ...` / `Invalid MCP server URL: ...`                                   | An `internal` MCP server must answer a `HEAD` from Danube and pass the outbound-address policy; one Danube cannot reach belongs under `local_only`                             |
| `400`  | `The connector declares no endpoints`                                                                     | Add at least one endpoint to the spec                                                                                                                                          |
| `400`  | `base_url is not reachable under this deployment's policy: ...`                                           | The hosted service refuses private addresses for an `internal` connector; register it as `local_only` and run the data-plane agent, which is what an in-network base URL needs |
| `403`  | `Insufficient permissions for this action`                                                                | You are a member but not an owner or admin                                                                                                                                     |
| `404`  | `Organization not found`                                                                                  | You are not a member of that organization, or the id is wrong                                                                                                                  |
| `409`  | `A service with this name already exists`                                                                 | A service you cannot manage already carries the name; choose another. A same-named service you do manage is updated instead                                                    |
| `413`  | `OpenAPI document is N bytes; the limit is 5,000,000 bytes`                                               | Trim the document                                                                                                                                                              |

Execution errors, in the `error_type` of a failed call:

| `error_type`                      | Meaning                                                                                       | Fix                                                                                            |
| --------------------------------- | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| `no_dataplane_agent`              | No agent of the organization has polled in the last two minutes                               | Start or enroll an agent; the Data Plane page shows who is online                              |
| `destination_blocked`             | The agent refused the host because it is outside `DANUBE_ALLOWED_DESTINATIONS`                | Add the host to the agent's allowlist                                                          |
| `dataplane_unsupported_service`   | A `local_only` service of a type the agent cannot run (a local stdio MCP server)              | Register the server's HTTP or SSE endpoint instead                                             |
| `upstream_rejected`               | A `local_only` MCP server ran the call and answered `isError`; the error text is the server's | Fix the arguments, or read the server's own message                                            |
| `upstream_tool_missing`           | The MCP server no longer offers the tool the catalog row points at                            | **Refresh tools** on the service page (runs through the agent)                                 |
| `credential_reference_unresolved` | The `env://` or `vault://` reference could not be resolved on the agent's host                | Set the variable on the agent, or check `VAULT_ADDR` / `VAULT_TOKEN` and the `#<field>` suffix |
| `auth_required`                   | No credential, personal or shared, for the service                                            | Store one (step 4)                                                                             |
| `usage_limit_reached`             | A per-key or organization cap                                                                 | Read `Retry-After` for the per-minute cap; raise or wait out the daily cap                     |

And the two refusals that come back as an HTTP status: `403 Your organization does not have access to this tool` (no grant, or an explicit deny; ask an admin for a grant) and `404 Tool not found` for someone outside the organization, which is deliberate so an internal tool's existence is not revealed.
