# How Credential Refresh Works for API Sources with Renew Endpoints

> Learn how credential refresh works for API sources with renew endpoints. Automatically update tokens and expiry times without OAuth refresh tokens.

- Repository: [Craft Ai Agents/craft-agents-oss](https://github.com/craft-ai-agents/craft-agents-oss)
- Tags: deep-dive
- Published: 2026-07-03

---

**Credential refresh for API sources with renew endpoints works by detecting sources configured with a `renewEndpoint`, automatically building HTTP requests to that endpoint with the current token, parsing the response for new tokens and expiry times, and updating the credential store without requiring OAuth refresh tokens.**

The craft-agents-oss repository implements a generic mechanism that allows API sources to maintain valid access tokens through custom HTTP renew endpoints. Unlike traditional OAuth refresh flows, this system supports any bearer-token API by interpreting a declarative `renewEndpoint` configuration. Understanding how credential refresh works for API sources with renew endpoints ensures your agents maintain uninterrupted access to external services even when initial tokens expire.

## Detecting Refreshable API Sources

The system identifies a source as refreshable when its configuration type is `api` and it includes a `renewEndpoint` definition. The helper function `sourceHasRenewEndpoint` in [`packages/shared/src/sources/types.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/sources/types.ts) (lines 221-229) performs this validation, returning true only when both conditions are met. This check enables the `TokenRefreshManager` to distinguish between static credentials and those requiring periodic renewal.

## Building the Renew HTTP Request

When a token expires, the `CredentialManager` constructs an HTTP request using the `ApiRenewEndpoint` configuration defined in [`packages/shared/src/sources/types.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/sources/types.ts) (lines 338-379). The build process follows a specific hierarchy:

- **URL** – Combines the source `baseUrl` with the `renewEndpoint.path`, or uses an absolute URL if provided.
- **Method** – Defaults to `POST` unless explicitly overridden in the configuration.
- **Headers** – Merges the source's default headers with `renewEndpoint.headers`, then automatically injects `Authorization: Bearer <current-token>` if not already present.
- **Body** – JSON-stringifies the request body after substituting `{{token}}` placeholders with the current access token.

This logic resides in [`packages/shared/src/sources/credential-manager.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/sources/credential-manager.ts) between lines 992 and 1034.

## Parsing the Response and Updating Stored Credentials

After executing the renew request, the credential manager processes the JSON response to extract authentication data. It reads the `tokenField` (defaulting to `access_token`) for the new token and the `expiresInField` (defaulting to `expires_in`) to calculate the expiration timestamp as `expiresAt = now + ttl * 1000`. If the response omits the expiry field, the optional `fallbackTtlSecs` value provides a safety net. The implementation in [`packages/shared/src/sources/credential-manager.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/sources/credential-manager.ts) (lines 1034-1047) handles this parsing, while line 1059 persists the updated token and expiry to the credential store.

## Orchestrating Periodic Refreshes

The `TokenRefreshManager` coordinates automatic refreshes by periodically invoking `ensureFreshToken` for all sources flagged as OAuth-based or containing a `renewEndpoint`. Located at line 251 of [`packages/shared/src/sources/token-refresh-manager.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/sources/token-refresh-manager.ts), this method calls the token getter generated by the credential manager for each source, ensuring tokens remain valid before they expire. If a renew request fails due to network errors, non-2xx responses, or missing token fields, the manager throws an exception, allowing the refresh loop to retry during the next cycle.

## Configuration and Code Examples

### Defining a Source with a Renew Endpoint

```typescript
// examples/src/sources.ts
import { SourceConfig } from '@craft-agents/shared/sources';

export const myApiSource: SourceConfig = {
  slug: 'my-api',
  type: 'api',
  baseUrl: 'https://api.example.com',
  defaultHeaders: { 'Accept': 'application/json' },
  api: {
    token: { /* initial token obtained elsewhere */ },
    renewEndpoint: {
      path: '/auth/refresh',          // relative to baseUrl
      method: 'POST',
      body: { grant_type: 'refresh_token', token: '{{token}}' },
      tokenField: 'access_token',
      expiresInField: 'expires_in',
      fallbackTtlSecs: 3600,
    },
  },
};

```

*Source file:* [`packages/shared/src/sources/types.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/sources/types.ts) – defines `ApiRenewEndpoint` and `SourceConfig` (see lines 338-379).

### Manually Triggering a Token Refresh

```typescript
import { CredentialManager } from '@craft-agents/shared/sources';

// Assume `source` is the config above
const credMgr = new CredentialManager();
await credMgr.refreshToken(source);   // triggers the renew request
const freshToken = await credMgr.getToken(source);
console.log('New token →', freshToken);

```

*Implementation:* `CredentialManager.refreshToken` (see [`credential-manager.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/credential-manager.ts) – lines 985-1059).

### Automatic Refresh in the Server

```typescript
import { TokenRefreshManager } from '@craft-agents/shared/sources';

const manager = new TokenRefreshManager();
await manager.ensureFreshToken([myApiSource]); // called periodically

```

*Implementation:* `TokenRefreshManager.ensureFreshToken` (see [`token-refresh-manager.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/token-refresh-manager.ts) – line 251).

## Summary

- API sources with a `renewEndpoint` configuration are detected as refreshable by the `sourceHasRenewEndpoint` utility in [`packages/shared/src/sources/types.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/sources/types.ts).
- The `CredentialManager` builds HTTP requests by merging headers, substituting `{{token}}` placeholders, and auto-injecting Bearer authorization.
- Responses are parsed for `access_token` and `expires_in` fields (with configurable defaults), calculating expiry timestamps using `fallbackTtlSecs` when necessary.
- `TokenRefreshManager` orchestrates periodic validation through `ensureFreshToken`, refreshing credentials proactively without OAuth refresh tokens.

## Frequently Asked Questions

### What makes an API source eligible for credential refresh?

An API source becomes eligible when its `type` is set to `api` and it defines a `renewEndpoint` object in its configuration. The `sourceHasRenewEndpoint` function in [`packages/shared/src/sources/types.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/sources/types.ts) validates this by checking for the presence of the endpoint definition.

### How does the system handle token substitution in renew requests?

The credential manager automatically replaces any `{{token}}` string found in the `renewEndpoint.headers` or `body` values with the current access token before sending the request. This substitution occurs in [`packages/shared/src/sources/credential-manager.ts`](https://github.com/craft-ai-agents/craft-agents-oss/blob/main/packages/shared/src/sources/credential-manager.ts) alongside the automatic injection of the `Authorization: Bearer` header.

### What happens if the renew endpoint response doesn't include an expiry time?

When the response lacks the configured `expiresInField` (default `expires_in`), the system falls back to the `fallbackTtlSecs` value specified in the `renewEndpoint` configuration. This ensures the token still receives a calculated expiration timestamp even when the API omits TTL information.

### Can I manually trigger a credential refresh for debugging?

Yes, you can instantiate the `CredentialManager` and call `await credMgr.refreshToken(source)` to trigger the renew flow immediately. This executes the same logic used by the automatic refresh scheduler, including request building, token substitution, and credential storage updates.