# Credentials

12 calls. Every request needs the `Authorization` header: see [the overview](https://docs.maitento.com/reference.md).

## `credentials.deleteByTag`

```http
DELETE /credentials
```

Deletes every credential with this tag (and takes them out of their stacks), and returns how many it deleted.

**Who can call it:** Tenant admins only, with an API key or a portal session. [Try it in the API playground](https://docs.maitento.com/playground/?call=DELETE%20%2Fcredentials).

**Query parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `tag` | `string` | yes |  |

**Response:** `integer` (200)

```bash title="curl"
curl -X DELETE "https://api.maitento.com/credentials" \
  -H "Authorization: $MAITENTO_AUTH"
```
```ts title="TypeScript"
const deleted = await mt.credentials.deleteByTag("customer-42");
```
```python title="Python"
mt.credentials.delete_by_tag("acme")
```

## `credentials.list`

```http
GET /credentials
```

Every credential of the tenant, by name; with `tag`, only those with that tag.

**Who can call it:** Admins and members, with an API key or a portal session. [Try it in the API playground](https://docs.maitento.com/playground/?call=GET%20%2Fcredentials).

**Query parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `tag` | `string` |  |  |

**Response:** [`ModelCredential`](https://docs.maitento.com/reference/types/model-credential.md)`[]` (200)

```bash title="curl"
curl -X GET "https://api.maitento.com/credentials" \
  -H "Authorization: $MAITENTO_AUTH"
```
```ts title="TypeScript"
const credentials = await mt.credentials.list({ tag: "customer-42" });
```
```python title="Python"
mt.credentials.list(tag="acme")
```

## `credentials.create`

```http
POST /credentials
```

Creates an API-key credential and returns it, with its id. Refused with `ValidationError` when the name is taken, a key or the endpoint is invalid, or the plan's credential limit is reached.

**Who can call it:** Tenant admins only, with an API key or a portal session. [Try it in the API playground](https://docs.maitento.com/playground/?call=POST%20%2Fcredentials).

**Request body:** [`ModelCredentialRequest`](https://docs.maitento.com/reference/types/model-credential-request.md)

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | `string` |  |  |
| `description` | `string \| null` |  |  |
| `provider` | [`ModelKeyProvider`](https://docs.maitento.com/reference/types/model-key-provider.md) |  |  |
| `endpoint` | `string \| null` |  | An HTTPS endpoint on the public internet; null for the provider's public API. |
| `cloud` | [`CredentialCloudInput`](https://docs.maitento.com/reference/types/credential-cloud.md#in-a-request)`\| null` |  | For an Anthropic credential: Claude on Amazon Bedrock (with `awsRegion`) or Google Vertex AI (with `gcpProjectId` and `gcpRegion`), with no `endpoint`. The key is then the Bedrock API key, or the service account's JSON key file. Only code pipelines' Claude Code agents run on it. |
| `keys` | [`ModelCredentialKeyRequest`](https://docs.maitento.com/reference/types/model-credential-key-request.md)`[]` |  |  |
| `models` | [`CredentialModelInput`](https://docs.maitento.com/reference/types/credential-model.md#in-a-request)`[]` |  |  |
| `tag` | `string \| null` |  |  |

**Response:** [`ModelCredential`](https://docs.maitento.com/reference/types/model-credential.md) (200)

```bash title="curl"
curl -X POST "https://api.maitento.com/credentials" \
  -H "Authorization: $MAITENTO_AUTH" \
  -H "Content-Type: application/json" \
  -d '{"name":"...","description":"...","provider":"Anthropic","endpoint":"...","cloud":{},"keys":[],"models":[],"tag":"..."}'
```
```ts title="TypeScript"
const credential = await mt.credentials.create({ name: "openai-production", provider: "OpenAi", keys: [{ value: process.env.OPENAI_API_KEY! }], models: [{ model: "gpt-5" }] });
```
```python title="Python"
mt.credentials.create(name="openai-production", provider="OpenAi", keys=[{"value": os.environ["OPENAI_API_KEY"]}], models=[{"model": "gpt-5"}])
```

## `credentials.delete`

```http
DELETE /credentials/by-id/{id}
```

Deletes a credential and takes it out of every stack that holds it.

**Who can call it:** Tenant admins only, with an API key or a portal session. [Try it in the API playground](https://docs.maitento.com/playground/?call=DELETE%20%2Fcredentials%2Fby-id%2F%7Bid%7D).

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | `uuid` | yes |  |

**Response:** no body (204)

```bash title="curl"
curl -X DELETE "https://api.maitento.com/credentials/by-id/<id>" \
  -H "Authorization: $MAITENTO_AUTH"
```
```ts title="TypeScript"
await mt.credentials.delete("6f1d2c3b-4a5e-4f60-8b7c-9d0e1f2a3b4c");
```
```python title="Python"
mt.credentials.delete("3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7")
```

## `credentials.get`

```http
GET /credentials/by-id/{id}
```

The credential with this id. Throws `NotFoundError` when there is none.

**Who can call it:** Admins and members, with an API key or a portal session. [Try it in the API playground](https://docs.maitento.com/playground/?call=GET%20%2Fcredentials%2Fby-id%2F%7Bid%7D).

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | `uuid` | yes |  |

**Response:** [`ModelCredential`](https://docs.maitento.com/reference/types/model-credential.md) (200)

```bash title="curl"
curl -X GET "https://api.maitento.com/credentials/by-id/<id>" \
  -H "Authorization: $MAITENTO_AUTH"
```
```ts title="TypeScript"
const credential = await mt.credentials.get("6f1d2c3b-4a5e-4f60-8b7c-9d0e1f2a3b4c");
```
```python title="Python"
mt.credentials.get("3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7")
```

## `credentials.update`

```http
PUT /credentials/by-id/{id}
```

Replaces a credential's fields. Keys are matched by id: one sent as `{ id }` keeps its secret, and one left out is removed. New keys or a new endpoint switch a credential its circuit breaker turned off back on.

**Who can call it:** Tenant admins only, with an API key or a portal session. [Try it in the API playground](https://docs.maitento.com/playground/?call=PUT%20%2Fcredentials%2Fby-id%2F%7Bid%7D).

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | `uuid` | yes |  |

**Request body:** [`ModelCredentialRequest`](https://docs.maitento.com/reference/types/model-credential-request.md)

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | `string` |  |  |
| `description` | `string \| null` |  |  |
| `provider` | [`ModelKeyProvider`](https://docs.maitento.com/reference/types/model-key-provider.md) |  |  |
| `endpoint` | `string \| null` |  | An HTTPS endpoint on the public internet; null for the provider's public API. |
| `cloud` | [`CredentialCloudInput`](https://docs.maitento.com/reference/types/credential-cloud.md#in-a-request)`\| null` |  | For an Anthropic credential: Claude on Amazon Bedrock (with `awsRegion`) or Google Vertex AI (with `gcpProjectId` and `gcpRegion`), with no `endpoint`. The key is then the Bedrock API key, or the service account's JSON key file. Only code pipelines' Claude Code agents run on it. |
| `keys` | [`ModelCredentialKeyRequest`](https://docs.maitento.com/reference/types/model-credential-key-request.md)`[]` |  |  |
| `models` | [`CredentialModelInput`](https://docs.maitento.com/reference/types/credential-model.md#in-a-request)`[]` |  |  |
| `tag` | `string \| null` |  |  |

**Response:** [`ModelCredential`](https://docs.maitento.com/reference/types/model-credential.md) (200)

```bash title="curl"
curl -X PUT "https://api.maitento.com/credentials/by-id/<id>" \
  -H "Authorization: $MAITENTO_AUTH" \
  -H "Content-Type: application/json" \
  -d '{"name":"...","description":"...","provider":"Anthropic","endpoint":"...","cloud":{},"keys":[],"models":[],"tag":"..."}'
```
```ts title="TypeScript"
await mt.credentials.update("6f1d2c3b-4a5e-4f60-8b7c-9d0e1f2a3b4c", { name: "openai-production", provider: "OpenAi", keys: [{ id: "3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7" }, { value: process.env.OPENAI_API_KEY_NEW! }] });
```
```python title="Python"
mt.credentials.update("3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7", name="openai-production", provider="OpenAi", keys=[{"id": "4f5a6b7c-8d9e-4f0a-b1c2-d3e4f5a6b7c8"}], models=[{"model": "gpt-5"}, {"model": "gpt-5-mini"}])  # the key keeps its secret
```

## `credentials.replaceSignInTokens`

```http
PUT /credentials/by-id/{id}/sign-in-tokens
```

Replaces the tokens of a credential made by `importSignIn`, after you refreshed them. A sign-in made through `startSignIn` is refused with `api:credentials:sign-in-refreshed-here`: Maitento refreshes those itself.

**Who can call it:** Tenant admins only, with an API key or a portal session. [Try it in the API playground](https://docs.maitento.com/playground/?call=PUT%20%2Fcredentials%2Fby-id%2F%7Bid%7D%2Fsign-in-tokens).

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | `uuid` | yes |  |

**Request body:** [`CredentialSignInTokensRequest`](https://docs.maitento.com/reference/types/credential-sign-in-tokens-request.md)

| Field | Type | Required | Description |
|---|---|---|---|
| `tool` | [`CredentialOAuthTool`](https://docs.maitento.com/reference/types/credential-o-auth-tool.md) |  | The coding agent the sign-in is for. |
| `accessToken` | `string` |  | The access token. Write-only. |
| `refreshToken` | `string` |  | The refresh token, which the coding agent may need during a long run. Write-only. |
| `idToken` | `string \| null` |  | The OpenID Connect ID token. Required for Codex; null for Claude. Write-only. |
| `expiresAtUtc` | `datetime` |  | When the access token expires (UTC). |
| `scopes` | `string[] \| null` |  | The scopes the tokens carry. |
| `subscriptionType` | `string \| null` |  | The subscription the sign-in belongs to, as the provider names it (for example `max`). |
| `accountName` | `string \| null` |  | Who signed in, as the provider shows it. |
| `name` | `string \| null` |  |  |
| `description` | `string \| null` |  |  |
| `tag` | `string \| null` |  |  |
| `keepFresh` | `boolean` |  | True: the platform keeps these tokens fresh from now on, with the refresh token, as it does for a sign-in made here. Stop refreshing them yourself: a refresh replaces the refresh token, so only one side can keep doing it. Replacing the tokens of a sign-in you've been refreshing with this set hands it over. Once handed over it can't be handed back; sign in again for new tokens. False (default): you keep them fresh. |

**Response:** [`ModelCredential`](https://docs.maitento.com/reference/types/model-credential.md) (200)

```bash title="curl"
curl -X PUT "https://api.maitento.com/credentials/by-id/<id>/sign-in-tokens" \
  -H "Authorization: $MAITENTO_AUTH" \
  -H "Content-Type: application/json" \
  -d '{"tool":"Claude","accessToken":"...","refreshToken":"...","idToken":"...","expiresAtUtc":"2026-01-01T00:00:00Z","scopes":[],"subscriptionType":"...","accountName":"...","name":"...","description":"...","tag":"...","keepFresh":false}'
```
```ts title="TypeScript"
await mt.credentials.replaceSignInTokens("6f1d2c3b-4a5e-4f60-8b7c-9d0e1f2a3b4c", { tool: "Claude", accessToken, refreshToken, expiresAtUtc });
```
```python title="Python"
mt.credentials.replace_sign_in_tokens("3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7", tool="Claude", access_token=os.environ["CLAUDE_ACCESS_TOKEN"], refresh_token=os.environ["CLAUDE_REFRESH_TOKEN"], expires_at_utc=datetime.datetime(2027, 4, 16, 17, tzinfo=datetime.timezone.utc))
```

## `credentials.test`

```http
POST /credentials/by-id/{id}/test
```

Tests a credential without running a model: each key lists the provider's models once at the credential's endpoint. The test passes when every key does, and a passing test switches a credential its circuit breaker turned off back on. Limited to a few a minute per tenant: beyond that it throws `RateLimitError`.

**Who can call it:** Tenant admins only, with an API key or a portal session. [Try it in the API playground](https://docs.maitento.com/playground/?call=POST%20%2Fcredentials%2Fby-id%2F%7Bid%7D%2Ftest).

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | `uuid` | yes |  |

**Response:** [`CredentialTestResult`](https://docs.maitento.com/reference/types/credential-test-result.md) (200)

```bash title="curl"
curl -X POST "https://api.maitento.com/credentials/by-id/<id>/test" \
  -H "Authorization: $MAITENTO_AUTH"
```
```ts title="TypeScript"
const { succeeded, errorCode } = await mt.credentials.test("6f1d2c3b-4a5e-4f60-8b7c-9d0e1f2a3b4c");
```
```python title="Python"
mt.credentials.test("3e4f5a6b-7c8d-4e9f-a0b1-c2d3e4f5a6b7")
```

## `credentials.importSignIn`

```http
POST /credentials/sign-in-tokens
```

Makes an OAuth credential from a sign-in whose tokens you already hold, such as one your own application signed in. You keep them fresh: Maitento never refreshes them, so call `replaceSignInTokens` each time you refresh. Tenant admins only. Never retried: a retry could make a second credential.

**Who can call it:** Tenant admins only, with an API key or a portal session. [Try it in the API playground](https://docs.maitento.com/playground/?call=POST%20%2Fcredentials%2Fsign-in-tokens).

**Request body:** [`CredentialSignInTokensRequest`](https://docs.maitento.com/reference/types/credential-sign-in-tokens-request.md)

| Field | Type | Required | Description |
|---|---|---|---|
| `tool` | [`CredentialOAuthTool`](https://docs.maitento.com/reference/types/credential-o-auth-tool.md) |  | The coding agent the sign-in is for. |
| `accessToken` | `string` |  | The access token. Write-only. |
| `refreshToken` | `string` |  | The refresh token, which the coding agent may need during a long run. Write-only. |
| `idToken` | `string \| null` |  | The OpenID Connect ID token. Required for Codex; null for Claude. Write-only. |
| `expiresAtUtc` | `datetime` |  | When the access token expires (UTC). |
| `scopes` | `string[] \| null` |  | The scopes the tokens carry. |
| `subscriptionType` | `string \| null` |  | The subscription the sign-in belongs to, as the provider names it (for example `max`). |
| `accountName` | `string \| null` |  | Who signed in, as the provider shows it. |
| `name` | `string \| null` |  |  |
| `description` | `string \| null` |  |  |
| `tag` | `string \| null` |  |  |
| `keepFresh` | `boolean` |  | True: the platform keeps these tokens fresh from now on, with the refresh token, as it does for a sign-in made here. Stop refreshing them yourself: a refresh replaces the refresh token, so only one side can keep doing it. Replacing the tokens of a sign-in you've been refreshing with this set hands it over. Once handed over it can't be handed back; sign in again for new tokens. False (default): you keep them fresh. |

**Response:** [`ModelCredential`](https://docs.maitento.com/reference/types/model-credential.md) (200)

```bash title="curl"
curl -X POST "https://api.maitento.com/credentials/sign-in-tokens" \
  -H "Authorization: $MAITENTO_AUTH" \
  -H "Content-Type: application/json" \
  -d '{"tool":"Claude","accessToken":"...","refreshToken":"...","idToken":"...","expiresAtUtc":"2026-01-01T00:00:00Z","scopes":[],"subscriptionType":"...","accountName":"...","name":"...","description":"...","tag":"...","keepFresh":false}'
```
```ts title="TypeScript"
const credential = await mt.credentials.importSignIn({ tool: "Claude", accessToken, refreshToken, expiresAtUtc, name: "acme-claude-max" });
```
```python title="Python"
mt.credentials.import_sign_in(tool="Claude", access_token=os.environ["CLAUDE_ACCESS_TOKEN"], refresh_token=os.environ["CLAUDE_REFRESH_TOKEN"], expires_at_utc=datetime.datetime(2027, 4, 16, 17, tzinfo=datetime.timezone.utc), name="acme-claude-max")
```

## `credentials.startSignIn`

```http
POST /credentials/sign-in/{tool}
```

Starts signing a coding agent's subscription in as an OAuth credential: `"Claude"` (Claude Code, on a Claude Pro or Max subscription) or `"Codex"` (on a ChatGPT subscription). Pass `{ credentialId }` to sign an existing one in again (new tokens, same credential and stacks). Code pipelines run their agents on such a credential in place of an API key. Signing in takes two steps. For Claude, the person opens `authorizeUrl`, signs in, and you pass the code Anthropic shows to `finishSignIn`. For Codex, the person opens `verificationUrl` and enters `userCode`, and you call `finishSignIn` every `pollIntervalSeconds` until it returns `Complete`. Either way, finish before `expiresAtUtc`.

**Who can call it:** Tenant admins only, with an API key or a portal session. [Try it in the API playground](https://docs.maitento.com/playground/?call=POST%20%2Fcredentials%2Fsign-in%2F%7Btool%7D).

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `tool` | [`CredentialOAuthTool`](https://docs.maitento.com/reference/types/credential-o-auth-tool.md) | yes |  |

**Request body:** [`CredentialSignInStartRequest`](https://docs.maitento.com/reference/types/credential-sign-in-start-request.md)

| Field | Type | Required | Description |
|---|---|---|---|
| `credentialId` | `uuid \| null` |  | To sign an existing OAuth credential in again (new tokens, same credential and stacks); null for a new one. |

**Response:** [`CredentialSignInStart`](https://docs.maitento.com/reference/types/credential-sign-in-start.md) (200)

```bash title="curl"
curl -X POST "https://api.maitento.com/credentials/sign-in/<tool>" \
  -H "Authorization: $MAITENTO_AUTH" \
  -H "Content-Type: application/json" \
  -d '{"credentialId":"..."}'
```
```ts title="TypeScript"
const { signInId, verificationUrl, userCode, pollIntervalSeconds } = await mt.credentials.startSignIn("Codex");
```
```python title="Python"
mt.credentials.start_sign_in("codex")
```

## `credentials.finishSignIn`

```http
POST /credentials/sign-in/{tool}/finish
```

Finishes a sign-in that `startSignIn` started. For Claude, pass the `code` Anthropic showed (as shown, `#` and all). For Codex, pass no code and call again every `pollIntervalSeconds` while it returns `Pending`. Once it returns `Complete`, `credential` is the saved credential; `name`, `description` and `tag` name a new one (a credential signed in again keeps its own).

**Who can call it:** Tenant admins only, with an API key or a portal session. [Try it in the API playground](https://docs.maitento.com/playground/?call=POST%20%2Fcredentials%2Fsign-in%2F%7Btool%7D%2Ffinish).

**Path parameters**

| Name | Type | Required | Description |
|---|---|---|---|
| `tool` | [`CredentialOAuthTool`](https://docs.maitento.com/reference/types/credential-o-auth-tool.md) | yes |  |

**Request body:** [`CredentialSignInFinishRequest`](https://docs.maitento.com/reference/types/credential-sign-in-finish-request.md)

| Field | Type | Required | Description |
|---|---|---|---|
| `signInId` | `uuid` |  |  |
| `code` | `string \| null` |  | The code Anthropic shows after signing in to Claude (as shown, `code#state` included). |
| `name` | `string \| null` |  |  |
| `description` | `string \| null` |  |  |
| `tag` | `string \| null` |  |  |

**Response:** [`CredentialSignInResult`](https://docs.maitento.com/reference/types/credential-sign-in-result.md) (200)

```bash title="curl"
curl -X POST "https://api.maitento.com/credentials/sign-in/<tool>/finish" \
  -H "Authorization: $MAITENTO_AUTH" \
  -H "Content-Type: application/json" \
  -d '{"signInId":"...","code":"...","name":"...","description":"...","tag":"..."}'
```
```ts title="TypeScript"
const { status, credential } = await mt.credentials.finishSignIn("Codex", { signInId: "9e8d7c6b-5a4f-4e3d-b2c1-a0f9e8d7c6b5", name: "team-chatgpt" });
```
```python title="Python"
mt.credentials.finish_sign_in("codex", sign_in_id="6b7c8d9e-0f1a-4b2c-9d3e-f4a5b6c7d8e9", name="codex-team")
```

## `credentials.testUnsaved`

```http
POST /credentials/test
```

Tests a credential before saving it, so a failing one never has to exist: the same checks and calls as `test`, on the keys in `request` (what `create` takes). Nothing is stored, and the result's `credentialId` is empty. Refused with `create`'s codes, and shares `test`'s limit.

**Who can call it:** Tenant admins only, with an API key or a portal session. [Try it in the API playground](https://docs.maitento.com/playground/?call=POST%20%2Fcredentials%2Ftest).

**Request body:** [`ModelCredentialRequest`](https://docs.maitento.com/reference/types/model-credential-request.md)

| Field | Type | Required | Description |
|---|---|---|---|
| `name` | `string` |  |  |
| `description` | `string \| null` |  |  |
| `provider` | [`ModelKeyProvider`](https://docs.maitento.com/reference/types/model-key-provider.md) |  |  |
| `endpoint` | `string \| null` |  | An HTTPS endpoint on the public internet; null for the provider's public API. |
| `cloud` | [`CredentialCloudInput`](https://docs.maitento.com/reference/types/credential-cloud.md#in-a-request)`\| null` |  | For an Anthropic credential: Claude on Amazon Bedrock (with `awsRegion`) or Google Vertex AI (with `gcpProjectId` and `gcpRegion`), with no `endpoint`. The key is then the Bedrock API key, or the service account's JSON key file. Only code pipelines' Claude Code agents run on it. |
| `keys` | [`ModelCredentialKeyRequest`](https://docs.maitento.com/reference/types/model-credential-key-request.md)`[]` |  |  |
| `models` | [`CredentialModelInput`](https://docs.maitento.com/reference/types/credential-model.md#in-a-request)`[]` |  |  |
| `tag` | `string \| null` |  |  |

**Response:** [`CredentialTestResult`](https://docs.maitento.com/reference/types/credential-test-result.md) (200)

```bash title="curl"
curl -X POST "https://api.maitento.com/credentials/test" \
  -H "Authorization: $MAITENTO_AUTH" \
  -H "Content-Type: application/json" \
  -d '{"name":"...","description":"...","provider":"Anthropic","endpoint":"...","cloud":{},"keys":[],"models":[],"tag":"..."}'
```
```ts title="TypeScript"
const { succeeded } = await mt.credentials.testUnsaved({ name: "openai-production", provider: "OpenAi", keys: [{ value: process.env.OPENAI_API_KEY! }] });
```
```python title="Python"
mt.credentials.test_unsaved(name="openai-production", provider="OpenAi", keys=[{"value": "sk-..."}])
```
