---
title: Broker a credential
description: Take the secret out of the agent entirely — including the material-key mistake that fails two layers from its cause.
---

The highest-value change you can make. An agent that holds an API key has that
key in its prompt context, its logs, and whatever the model says next. Move it
into the broker and prompt injection stops being credential theft.

## Register the credential

```bash
curl -X POST https://veefore.example.com/v1/credentials \
  -H "Authorization: Bearer $VEEFORE_API_KEY" \
  -H 'content-type: application/json' \
  -d '{
    "key": "payments-api-key",
    "name": "Payments service API key",
    "type": "API_KEY",
    "provider": "local",
    "material": {
      "api_key": "sk_live_…",
      "header": "x-api-key"
    }
  }'
```

The response is metadata. No endpoint ever returns the material.

## Get `material` right

:::danger This is the mistake that costs an afternoon
Each credential type reads **specific keys** from `material` and discards the
rest. Put the secret under a name the broker does not read and the failure
appears two layers from its cause: Veefore seals and stores it, permits the
action, brokers nothing usable, and your downstream returns `401`. Nothing in
the Veefore logs looks wrong, because from Veefore's point of view nothing was.

Veefore now refuses this at registration and at rotation with
`INVALID_CREDENTIAL_REFERENCE` rather than letting you find out in production.
:::

| Type | Reads |
| --- | --- |
| `API_KEY` | `api_key` / `apiKey` / `key` / `value`, plus optional `header`, `prefix` |
| `BEARER_TOKEN` | `token` / `access_token` / `value` |
| `BASIC_AUTH` | `username` and `password` |
| `DATABASE_DSN` | `dsn` / `url` / `connection_string` |
| `OAUTH_CLIENT` | `client_id` and `client_secret` |
| `MTLS_CERTIFICATE` | `certificate` and `private_key` |
| `SIGNING_KEY` | `private_key` and `algorithm` |

`BASIC_AUTH`, `MTLS_CERTIFICATE` and `SIGNING_KEY` are exempt from the
single-secret check because their material is legitimately multi-field.

`header` and `prefix` control how an `API_KEY` is presented downstream:

```json
{ "api_key": "sk_live_…", "header": "x-api-key" }
{ "api_key": "sk_live_…", "header": "authorization", "prefix": "Bearer " }
```

## Attach it to a tool

```bash
curl -X POST https://veefore.example.com/v1/tools \
  -H "Authorization: Bearer $VEEFORE_API_KEY" \
  -H 'content-type: application/json' \
  -d '{
    "workspace_id": "wsp_01JAV…",
    "key": "refund_customer",
    "name": "Refund a customer order",
    "kind": "HTTP",
    "required_capability": "refund.create",
    "endpoint_url": "https://payments.internal.example.com/refunds",
    "destination_allowlist": ["payments.internal.example.com"],
    "credential_id": "cred_01JAV…"
  }'
```

That is the whole integration. The agent never references the credential, never
receives it, and does not need to know it exists.

## What happens at request time

Stage 10 of the pipeline — after authentication, identity, lifecycle,
capability, delegation, policy, risk and approval have all passed:

1. The broker unseals the material.
2. It issues a **lease**: a short-lived credential scoped to this action.
3. The tool runtime injects it into the outbound request.
4. The lease is recorded by reference and expires.

Default lease lifetime is `CREDENTIAL_DEFAULT_TTL_SECONDS`, 600 seconds. Lease
statuses: `ISSUED`, `USED`, `EXPIRED`, `REVOKED`.

:::security Why stage 10 and not earlier
Brokering is the first irreversible step. Everything before it can refuse with
no trace outside Veefore.

Putting the broker last is what makes `BLOCK` mean blocked. Brokering earlier —
say, for latency — would mean a blocked action had already caused a credential
to be issued, and "we blocked it" and "the secret did not go out" would become
two different claims with only one of them true.
:::

## Migrating an agent that already holds a key

:::steps

:::step Register the existing key as a credential

Same value, under the right material key for its type.
:::

:::step Attach it to the tool and authorize the agent

The agent can now reach the downstream through Veefore.
:::

:::step Switch the agent's calls to `act()`

Where it previously called your API directly with the key, it now calls
`agent.act({ tool: 'refund_customer', … })`.
:::

:::step Remove the key from the agent's environment

This is the step that actually achieves something. Until you do it, the agent
still holds a usable credential and nothing has improved.
:::

:::step Rotate

The old value was in an agent's environment, possibly in its logs, possibly in a
model provider's request history. Treat it as exposed.

```bash
curl -X POST https://veefore.example.com/v1/credentials/cred_01ABC/rotate \
  -H "Authorization: Bearer $VEEFORE_API_KEY" \
  -H 'content-type: application/json' \
  -d '{ "material": { "api_key": "sk_live_NEW…", "header": "x-api-key" } }'
```
:::

:::

## Rotation and revocation

Rotation invalidates **every outstanding lease**, so an in-flight action holding
an old lease fails rather than continuing with a credential you decided to
retire. Status moves through `ROTATING` while it happens.

```bash
curl -X POST https://veefore.example.com/v1/credentials/cred_01ABC/revoke \
  -H "Authorization: Bearer $VEEFORE_API_KEY" \
  -H 'content-type: application/json' \
  -d '{ "reason": "Key published in a support ticket" }'
```

Revocation is immediate. Every tool depending on the credential starts failing
at stage 10 with `INVALID_CREDENTIAL_REFERENCE`.

Both `credential:rotate` and `credential:revoke` require step-up
authentication, and `credential:revoke` is high impact.

## Diagnosing a downstream 401

Veefore allowed the action and your downstream rejected what it received. In
order of likelihood:

1. **Wrong material key for the type.** `api_key` versus `token` versus `key`. Now caught at registration, but an older credential may predate the check — rotate it and the check runs.
2. **Wrong `header`.** Your downstream wants `x-api-key`, you configured `authorization`, or vice versa.
3. **Missing `prefix`.** `authorization` usually needs `Bearer ` including the trailing space.
4. **The key itself is wrong or revoked downstream.**

`veefore logs --limit 20` shows the stage results. A `CREDENTIAL` stage of
`PASS` with an `EXECUTION` stage carrying `http_401` means Veefore brokered
something and your downstream did not accept it — which narrows it to the first
three.

## Providers

`CREDENTIAL_BROKER_PROVIDER` selects the backend. `local` seals material in
PostgreSQL with `VEEFORE_DATA_ENCRYPTION_KEY`, which is right for development
and for deployments where the Veefore database is already your trust boundary.

For production, point it at your existing secret manager so material lives where
the rest of your secrets live and inherits that system's rotation, auditing and
access control. Broker behaviour is identical either way.

:::warning Rotating the data encryption key requires re-sealing
`VEEFORE_DATA_ENCRYPTION_KEY` seals stored material under the `local` provider.
Changing it without re-sealing makes every credential unreadable at once, and
the symptom is every tool failing simultaneously. Plan it as a migration.
:::

## Related

:::cards

:::card Credential broker | /concepts/credentials
The model and the guarantees.
:::

:::card Register a tool | /build/register-a-tool
Where `credential_id` is attached.
:::

:::card Secrets | /secure/secrets
Every place a secret could surface, and what stops it.
:::

:::card Credentials API | /reference/api/credentials
Register, rotate, revoke, read metadata.
:::

:::
