볼트로 인증하기
볼트로 인증하기 (Authenticate with vaults)
볼트(vault)와 자격 증명(credential)은 인증 프리미티브로, 타사 서비스용 자격 증명을 한 번 등록해 두고 세션을 만들 때 ID로 참조할 수 있게 해줘요. 이렇게 하면 자체 시크릿 저장소를 운영하거나, 매 호출마다 토큰을 전송하거나, 에이전트가 어떤 최종 사용자를 대신해서 행동했는지 잃어버릴 걱정을 하지 않아도 돼요. 볼트 참조는 세션별 파라미터라서 agent 리소스 단위로 제품을, session 리소스 단위로 사용자를 관리할 수 있어요.
출처: 문서
본문
볼트와 자격 증명은 인증 프리미티브로, 타사 서비스용 자격 증명을 한 번 등록해 두고 세션을 만들 때 ID로 참조할 수 있게 해줘요. 이렇게 하면 자체 시크릿 저장소를 운영하거나, 매 호출마다 토큰을 전송하거나, 에이전트가 어떤 최종 사용자를 대신해서 행동했는지 잃어버릴 걱정을 하지 않아도 돼요.
볼트 참조는 세션별 파라미터이므로 agent 리소스 단위로 제품을 관리하고, session 리소스 단위로 사용자를 관리할 수 있어요.
볼트 만들기 (Create a vault)
볼트는 최종 사용자와 연결된 credentials의 모음이에요. 그것에 display_name을 주고 선택적으로 metadata로 태그해서 여러분의 사용자 기록으로 돌려 매핑할 수 있어요.
<File filename="alice.vault.yaml">
```yaml
display_name: Alice
metadata:
external_user_id: usr_abc123
```
</File>
vault = client.beta.vaults.create(
display_name="Alice",
metadata={"external_user_id": "usr_abc123"},
)
print(vault.id) # "vlt_01ABC..."
const vault = await client.beta.vaults.create({
display_name: "Alice",
metadata: { external_user_id: "usr_abc123" },
});
console.log(vault.id); // "vlt_01ABC..."
var vault = await client.Beta.Vaults.Create(new()
{
DisplayName = "Alice",
Metadata = new Dictionary<string, string> { ["external_user_id"] = "usr_abc123" },
});
Console.WriteLine(vault.ID); // "vlt_01ABC..."
vault, err := client.Beta.Vaults.New(ctx, anthropic.BetaVaultNewParams{
DisplayName: "Alice",
Metadata: map[string]string{"external_user_id": "usr_abc123"},
})
if err != nil {
panic(err)
}
fmt.Println(vault.ID) // "vlt_01ABC..."
var vault = client.beta().vaults().create(VaultCreateParams.builder()
.displayName("Alice")
.metadata(VaultCreateParams.Metadata.builder()
.putAdditionalProperty("external_user_id", JsonValue.from("usr_abc123"))
.build())
.build());
IO.println(vault.id()); // "vlt_01ABC..."
$vault = $client->beta->vaults->create(
displayName: 'Alice',
metadata: ['external_user_id' => 'usr_abc123'],
);
echo $vault->id . "\n"; // "vlt_01ABC..."
vault = client.beta.vaults.create(
display_name: "Alice",
metadata: {external_user_id: "usr_abc123"}
)
puts vault.id # "vlt_01ABC..."
응답은 전체 볼트 기록이에요:
{
"type": "vault",
"id": "vlt_01ABC...",
"display_name": "Alice",
"metadata": { "external_user_id": "usr_abc123" },
"created_at": "2026-03-18T10:00:00Z",
"updated_at": "2026-03-18T10:00:00Z",
"archived_at": null
}
자격 증명 추가하기 (Add a credential)
두 가지 자격 증명 범주가 지원돼요:
- MCP 자격 증명 (
mcp_oauth,static_bearer): 각 자격 증명은mcp_server_url로 키가 지정돼요. 세션 런타임에서 에이전트가 해당 URL의 서버에 연결하면 토큰이 자동으로 주입돼요. - 환경 변수 (
environment_variable): 각 자격 증명은secret_name(환경 변수 이름)으로 키가 지정되고 샌드박스에 불투명한 플레이스홀더로 저장돼요. 에이전트가 아웃바운드 요청을 시작하면 플레이스홀더가 이그레스(egress) 시점에 실제 시크릿으로 치환돼요. 에이전트는 시크릿 값을 결코 보지 못해요. CLI, SDK, 직접 API 호출처럼 환경 변수로 인증하는 모든 서비스에 사용하세요.
여러분이 제공하는 실제 자격 증명 값(token, access_token, refresh_token, client_secret, secret_value)은 민감한 쓰기 전용 필드로 취급되며 API 응답에 결코 반환되지 않아요.
`refresh.token_endpoint_auth.type` 필드는 갱신 호출을 어떻게 인증할지 나타내요:
* `none`: 공개 클라이언트
* `client_secret_basic`: 클라이언트 시크릿을 사용한 HTTP Basic 인증
* `client_secret_post`: POST 본문 속의 클라이언트 시크릿
<CodeGroup>
```bash cURL
curl --fail-with-body -sS "https://api.anthropic.com/v1/vaults/$VAULT_ID/credentials" \
-H "x-api-key: $ANTHR...KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <<'EOF'
{
"display_name": "Alice's Slack",
"auth": {
"type": "mcp_oauth",
"mcp_server_url": "https://mcp.slack.com/mcp",
"access_token": "xoxp-...",
"expires_at": "2099-12-31T23:59:59Z",
"refresh": {
"token_endpoint": "https://slack.com/api/oauth.v2.user.access",
"client_id": "1234567890.0987654321",
"scope": "channels:read chat:write",
"refresh_token": "xoxe-1-...",
"token_endpoint_auth": {"type": "client_secret_post", "client_secret": "abc123..."}
}
}
}
EOF
```
```bash CLI
ant beta:vaults:credentials create \
--vault-id "$VAULT_ID" \
--display-name "Alice's Slack" <<'YAML'
auth:
type: mcp_oauth
mcp_server_url: https://mcp.slack.com/mcp
access_token: xoxp-...
expires_at: "2099-12-31T23:59:59Z"
refresh:
token_endpoint: https://slack.com/api/oauth.v2.user.access
client_id: "1234567890.0987654321"
scope: channels:read chat:write
refresh_token: xoxe-1-...
token_endpoint_auth:
type: client_secret_post
client_secret: abc123...
YAML
```
```python Python
credential = client.beta.vaults.credentials.create(
vault_id=vault.id,
display_name="Alice's Slack",
auth={
"type": "mcp_oauth",
"mcp_server_url": "https://mcp.slack.com/mcp",
"access_token": "xoxp-...",
"expires_at": "2099-12-31T23:59:59Z",
"refresh": {
"token_endpoint": "https://slack.com/api/oauth.v2.user.access",
"client_id": "1234567890.0987654321",
"scope": "channels:read chat:write",
"refresh_token": "xoxe-1-...",
"token_endpoint_auth": {"type": "client_secret_post", "client_secret": "abc123..."},
},
},
)
```
```typescript TypeScript
const credential = await client.beta.vaults.credentials.create(vault.id, {
display_name: "Alice's Slack",
auth: {
type: "mcp_oauth",
mcp_server_url: "https://mcp.slack.com/mcp",
access_token: "xoxp-...",
expires_at: "2099-12-31T23:59:59Z",
refresh: {
token_endpoint: "https://slack.com/api/oauth.v2.user.access",
client_id: "1234567890.0987654321",
scope: "channels:read chat:write",
refresh_token: "xoxe-1-...",
token_endpoint_auth: {
type: "client_secret_post",
client_secret: "abc123...",
},
},
},
});
```
```csharp C#
var credential = await client.Beta.Vaults.Credentials.Create(vault.ID, new()
{
DisplayName = "Alice's Slack",
Auth = new BetaManagedAgentsMcpOAuthCreateParams
{
Type = BetaManagedAgentsMcpOAuthCreateParamsType.McpOAuth,
McpServerUrl = "https://mcp.slack.com/mcp",
AccessToken = "xoxp-...",
ExpiresAt = DateTimeOffset.Parse("2099-12-31T23:59:59Z"),
Refresh = new()
{
TokenEndpoint = "https://slack.com/api/oauth.v2.user.access",
ClientID = "1234567890.0987654321",
Scope = "channels:read chat:write",
RefreshToken = "xoxe-1-...",
TokenEndpointAuth = new BetaManagedAgentsTokenEndpointAuthPostParam
{
Type = BetaManagedAgentsTokenEndpointAuthPostParamType.ClientSecretPost,
ClientSecret = "abc123...",
},
},
},
});
```
```go Go
credential, err := client.Beta.Vaults.Credentials.New(ctx, vault.ID, anthropic.BetaVaultCredentialNewParams{
DisplayName: anthropic.String("Alice's Slack"),
Auth: anthropic.BetaVaultCredentialNewParamsAuthUnion{
OfMCPOAuth: &anthropic.BetaManagedAgentsMCPOAuthCreateParams{
Type: anthropic.BetaManagedAgentsMCPOAuthCreateParamsTypeMCPOAuth,
MCPServerURL: "https://mcp.slack.com/mcp",
AccessToken: "xoxp-...",
ExpiresAt: anthropic.Time(time.Date(2099, time.December, 31, 23, 59, 59, 0, time.UTC)),
Refresh: anthropic.BetaManagedAgentsMCPOAuthRefreshParams{
TokenEndpoint: "https://slack.com/api/oauth.v2.user.access",
ClientID: "1234567890.0987654321",
Scope: anthropic.String("channels:read chat:write"),
RefreshToken: "xoxe-1-...",
TokenEndpointAuth: anthropic.BetaManagedAgentsMCPOAuthRefreshParamsTokenEndpointAuthUnion{
OfClientSecretPost: &anthropic.BetaManagedAgentsTokenEndpointAuthPostParam{
Type: anthropic.BetaManagedAgentsTokenEndpointAuthPostParamTypeClientSecretPost,
ClientSecret: "abc123...",
},
},
},
},
},
})
if err != nil {
panic(err)
}
```
```java Java
var credential = client.beta().vaults().credentials().create(vault.id(),
CredentialCreateParams.builder()
.displayName("Alice's Slack")
.auth(BetaManagedAgentsMcpOAuthCreateParams.builder()
.type(BetaManagedAgentsMcpOAuthCreateParams.Type.MCP_OAUTH)
.mcpServerUrl("https://mcp.slack.com/mcp")
.accessToken("xoxp-...")
.expiresAt(OffsetDateTime.parse("2099-12-31T23:59:59Z"))
.refresh(BetaManagedAgentsMcpOAuthRefreshParams.builder()
.tokenEndpoint("https://slack.com/api/oauth.v2.user.access")
.clientId("1234567890.0987654321")
.scope("channels:read chat:write")
.refreshToken("xoxe-1-...")
.clientSecretPostTokenEndpointAuth("abc123...")
.build())
.build())
.build());
```
```php PHP
$credential = $client->beta->vaults->credentials->create(
vaultID: $vault->id,
displayName: "Alice's Slack",
auth: ManagedAgentsMCPOAuthCreateParams::with(
type: 'mcp_oauth',
mcpServerURL: 'https://mcp.slack.com/mcp',
accessToken: 'xoxp-...',
expiresAt: new DateTimeImmutable('2099-12-31T23:59:59Z'),
refresh: ManagedAgentsMCPOAuthRefreshParams::with(
tokenEndpoint: 'https://slack.com/api/oauth.v2.user.access',
clientID: '1234567890.0987654321',
scope: 'channels:read chat:write',
refreshToken: 'xoxe-1-...',
tokenEndpointAuth: ManagedAgentsTokenEndpointAuthPostParam::with(
type: 'client_secret_post',
clientSecret: 'abc123...',
),
),
),
);
```
```ruby Ruby
credential = client.beta.vaults.credentials.create(
vault.id,
display_name: "Alice's Slack",
auth: {
type: "mcp_oauth",
mcp_server_url: "https://mcp.slack.com/mcp",
access_token: "xoxp-...",
expires_at: "2099-12-31T23:59:59Z",
refresh: {
token_endpoint: "https://slack.com/api/oauth.v2.user.access",
client_id: "1234567890.0987654321",
scope: "channels:read chat:write",
refresh_token: "xoxe-1-...",
token_endpoint_auth: {
type: "client_secret_post",
client_secret: "abc123..."
}
}
}
)
```
</CodeGroup>
`refresh.token_endpoint`을 갱신 토큰을 발급한 OAuth 흐름의 토큰 엔드포인트로 설정하세요. Anthropic이 모든 갱신 요청을 그 URL로 보내고, 그 필드는 자격 증명이 생성된 뒤에는 변경할 수 없기 때문이에요.
<CodeGroup>
```bash cURL
curl --fail-with-body -sS "https://api.anthropic.com/v1/vaults/$VAULT_ID/credentials" \
-H "x-api-key: $ANTHR...KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <<'EOF'
{
"display_name": "Linear API key",
"auth": {
"type": "static_bearer",
"mcp_server_url": "https://mcp.linear.app/mcp",
"token": "lin_api_your_linear_key"
}
}
EOF
```
```bash CLI
ant beta:vaults:credentials create --vault-id "$VAULT_ID" <<'YAML'
display_name: Linear API key
auth:
type: static_bearer
mcp_server_url: https://mcp.linear.app/mcp
token: lin_api_your_linear_key
YAML
```
```python Python
bearer_credential = client.beta.vaults.credentials.create(
vault_id=vault.id,
display_name="Linear API key",
auth={
"type": "static_bearer",
"mcp_server_url": "https://mcp.linear.app/mcp",
"token": "lin_api_your_linear_key",
},
)
```
```typescript TypeScript
const bearerCredential = await client.beta.vaults.credentials.create(vault.id, {
display_name: "Linear API key",
auth: {
type: "static_bearer",
mcp_server_url: "https://mcp.linear.app/mcp",
token: "lin_api_your_linear_key",
},
});
```
```csharp C#
var bearerCredential = await client.Beta.Vaults.Credentials.Create(vault.ID, new()
{
DisplayName = "Linear API key",
Auth = new BetaManagedAgentsStaticBearerCreateParams
{
Type = BetaManagedAgentsStaticBearerCreateParamsType.StaticBearer,
McpServerUrl = "https://mcp.linear.app/mcp",
Token = "lin_api_your_linear_key",
},
});
```
```go Go
bearerCredential, err := client.Beta.Vaults.Credentials.New(ctx, vault.ID, anthropic.BetaVaultCredentialNewParams{
DisplayName: anthropic.String("Linear API key"),
Auth: anthropic.BetaVaultCredentialNewParamsAuthUnion{
OfStaticBearer: &anthropic.BetaManagedAgentsStaticBearerCreateParams{
Type: anthropic.BetaManagedAgentsStaticBearerCreateParamsTypeStaticBearer,
MCPServerURL: "https://mcp.linear.app/mcp",
Token: "lin_api_your_linear_key",
},
},
})
if err != nil {
panic(err)
}
_ = bearerCredential
```
```java Java
var bearerCredential = client.beta().vaults().credentials().create(vault.id(),
CredentialCreateParams.builder()
.displayName("Linear API key")
.auth(BetaManagedAgentsStaticBearerCreateParams.builder()
.type(BetaManagedAgentsStaticBearerCreateParams.Type.STATIC_BEARER)
.mcpServerUrl("https://mcp.linear.app/mcp")
.token("lin_api_your_linear_key")
.build())
.build());
```
```php PHP
$bearerCredential = $client->beta->vaults->credentials->create(
vaultID: $vault->id,
displayName: 'Linear API key',
auth: ManagedAgentsStaticBearerCreateParams::with(
type: 'static_bearer',
mcpServerURL: 'https://mcp.linear.app/mcp',
token: 'lin_api_your_linear_key',
),
);
```
```ruby Ruby
bearer_credential = client.beta.vaults.credentials.create(
vault.id,
display_name: "Linear API key",
auth: {
type: "static_bearer",
mcp_server_url: "https://mcp.linear.app/mcp",
token: "lin_api_your_linear_key"
}
)
```
</CodeGroup>
`networking.allowed_hosts` 배열은 시크릿이 치환될 수 있는 아웃바운드 호스트를 제어해요. 특정 목록과 함께 `"type": "limited"`를 사용하거나, 호출자가 미리 열거할 수 없는 도메인에 닿는다면 `"type": "unrestricted"`를 사용하세요.
보안 목적상 도메인 제한을 강력히 권장하며, 키가 승인되지 않은 호스트와 공유되는 일을 막아줘요.
<Note>
볼트 자격 증명의 `networking.allowed_hosts`는 어떤 요청이 시크릿을 사용하는지를 제어하지, 어떤 요청이 허용되는지를 제어하지 않아요. 에이전트가 실제로 도메인에 닿으려면 [환경 수준](https://platform.claude.com/docs/en/managed-agents/environments)에서도 허용되어야 해요. 시크릿이 치환된 요청이 성공하려면 두 수준 모두 해당 도메인을(무제한 네트워킹 또는 `allowed_hosts`에 도메인을 명시적으로 나열해) 포함해야 해요.
</Note>
선택적 `injection_location` 필드는 시크릿이 치환되는 위치를 한정해요; 전체 의미는 예시 뒤에 나와요.
<CodeGroup>
```bash cURL
curl --fail-with-body -sS "https://api.anthropic.com/v1/vaults/$VAULT_ID/credentials" \
-H "x-api-key: $ANTHR...KEY" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
--data @- <<'EOF'
{
"auth": {
"type": "environment_variable",
"secret_name": "NOTION_API_KEY",
"secret_value": "ntn_your-secret-here",
"networking": {
"type": "limited",
"allowed_hosts": ["api.notion.com"]
},
"injection_location": {"header": true}
},
"display_name": "Notion API key for sandbox"
}
EOF
```
```bash CLI
ant beta:vaults:credentials create --vault-id "$VAULT_ID" <<'YAML'
display_name: Notion API key for sandbox
auth:
type: environment_variable
secret_name: NOTION_API_KEY
secret_value: ntn_your-secret-here
injection_location:
header: true
networking:
type: limited
allowed_hosts: [api.notion.com]
YAML
```
```python Python
env_credential = client.beta.vaults.credentials.create(
vault_id=vault.id,
display_name="Notion API key for sandbox",
auth={
"type": "environment_variable",
"secret_name": "NOTION_API_KEY",
"secret_value": "ntn_your-secret-here",
"networking": {
"type": "limited",
"allowed_hosts": ["api.notion.com"],
},
"injection_location": {"header": True},
},
)
if env_credential.auth.type == "environment_variable":
location = env_credential.auth.injection_location
print(f"header: {location.header}, body: {location.body}") # header: True, body: False
```
```typescript TypeScript
const envVarCredential = await client.beta.vaults.credentials.create(vault.id, {
display_name: "Notion API key for sandbox",
auth: {
type: "environment_variable",
secret_name: "NOTION_API_KEY",
secret_value: "ntn_your-secret-here",
networking: {
type: "limited",
allowed_hosts: ["api.notion.com"],
},
injection_location: { header: true },
},
});
if (envVarCredential.auth.type === "environment_variable") {
console.log(envVarCredential.auth.injection_location); // { header: true, body: false }
}
```
```csharp C#
var envVarCredential = await client.Beta.Vaults.Credentials.Create(vault.ID, new()
{
DisplayName = "Notion API key for sandbox",
Auth = new BetaManagedAgentsEnvironmentVariableCreateParams
{
Type = BetaManagedAgentsEnvironmentVariableCreateParamsType.EnvironmentVariable,
SecretName = "NOTION_API_KEY",
SecretValue = "ntn_your-secret-here",
Networking = new BetaManagedAgentsLimitedCredentialNetworkingParams
{
Type = BetaManagedAgentsLimitedCredentialNetworkingParamsType.Limited,
AllowedHosts = ["api.notion.com"],
},
InjectionLocation = new() { Header = true },
},
});
if (envVarCredential.Auth.TryPickBetaManagedAgentsEnvironmentVariableAuthResponse(out var envVarAuth))
{
var injectionLocation = envVarAuth.InjectionLocation;
Console.WriteLine($"Header: {injectionLocation.Header}, Body: {injectionLocation.Body}"); // "Header: True, Body: False"
}
```
```go Go
envVarCredential, err := client.Beta.Vaults.Credentials.New(ctx, vault.ID, anthropic.BetaVaultCredentialNewParams{
DisplayName: anthropic.String("Notion API key for sandbox"),
Auth: anthropic.BetaVaultCredentialNewParamsAuthUnion{
OfEnvironmentVariable: &anthropic.BetaManagedAgentsEnvironmentVariableCreateParams{
Type: anthropic.BetaManagedAgentsEnvironmentVariableCreateParamsTypeEnvironmentVariable,
SecretName: "NOTION_API_KEY",
SecretValue: "ntn_your-secret-here",
Networking: anthropic.BetaManagedAgentsCredentialNetworkingParamsUnion{
OfLimited: &anthropic.BetaManagedAgentsLimitedCredentialNetworkingParams{
Type: anthropic.BetaManagedAgentsLimitedCredentialNetworkingParamsTypeLimited,
AllowedHosts: []string{"api.notion.com"},
},
},
InjectionLocation: anthropic.BetaManagedAgentsInjectionLocationParams{
Header: anthropic.Bool(true),
},
},
},
})
if err != nil {
panic(err)
}
if envVarAuth, ok := envVarCredential.Auth.AsAny().(anthropic.BetaManagedAgentsEnvironmentVariableAuthResponse); ok {
injectionLocation := envVarAuth.InjectionLocation
fmt.Printf("Header:%t Body:%t\n", injectionLocation.Header, injectionLocation.Body) // "Header:true Body:false"
}
```
```java Java
var envVarCredential = client.beta().vaults().credentials().create(vault.id(),
CredentialCreateParams.builder()
.displayName("Notion API key for sandbox")
.auth(BetaManagedAgentsEnvironmentVariableCreateParams.builder()
.type(BetaManagedAgentsEnvironmentVariableCreateParams.Type.ENVIRONMENT_VARIABLE)
.secretName("NOTION_API_KEY")
.secretValue("ntn_your-secret-here")
.limitedNetworking(List.of("api.notion.com"))
.injectionLocation(BetaManagedAgentsInjectionLocationParams.builder()
.header(true)
.build())
.build())
.build());
envVarCredential.auth().environmentVariable().ifPresent(envVarAuth -> {
var injectionLocation = envVarAuth.injectionLocation();
IO.println("header=" + injectionLocation.header() + " body=" + injectionLocation.body()); // header=true body=false
});
```
```php PHP
$envVarCredential = $client->beta->vaults->credentials->create(
vaultID: $vault->id,
displayName: 'Notion API key for sandbox',
auth: ManagedAgentsEnvironmentVariableCreateParams::with(
type: ManagedAgentsEnvironmentVariableCreateParams\Type::ENVIRONMENT_VARIABLE,
secretName: 'NOTION_API_KEY',
secretValue: 'ntn_your-secret-here',
networking: ManagedAgentsLimitedCredentialNetworkingParams::with(
type: ManagedAgentsLimitedCredentialNetworkingParams\Type::LIMITED,
allowedHosts: ['api.notion.com'],
),
injectionLocation: ManagedAgentsInjectionLocationParams::with(header: true),
),
);
if ($envVarCredential->auth instanceof \Anthropic\Beta\Vaults\Credentials\ManagedAgentsEnvironmentVariableAuthResponse) {
$injectionLocation = $envVarCredential->auth->injectionLocation;
echo 'header: ' . json_encode($injectionLocation->header) . "\n"; // header: true
echo 'body: ' . json_encode($injectionLocation->body) . "\n"; // body: false
}
```
```ruby Ruby
env_credential = client.beta.vaults.credentials.create(
vault.id,
display_name: "Notion API key for sandbox",
auth: {
type: "environment_variable",
secret_name: "NOTION_API_KEY",
secret_value: "ntn_your-secret-here",
networking: {
type: "limited",
allowed_hosts: ["api.notion.com"]
},
injection_location: {header: true}
}
)
if env_credential.auth.type == :environment_variable
env_credential.auth.injection_location => {header:, body:}
puts "header: #{header}, body: #{body}" # header: true, body: false
end
```
</CodeGroup>
요청 페이로드는 에이전트가 작업하는 콘텐츠에서 조립되는 경우가 많아서 요청 본문이 더 넓은 노출 표면이에요. 대부분의 서비스는 요청 헤더에서 API 키를 읽으므로 `header`만 활성화하는 것이 더 좁은 구성이에요. 해당 자격 증명에 대해 요청 헤더 값으로의 치환만 한정해요.
자격 증명의 `injection_location`은 아웃바운드 요청의 어느 부분에 시크릿이 치환되는지 제어해요. `networking`의 형제인 선택적 객체이며, 두 Boolean 필드 `header`(요청 헤더)와 `body`(요청 본문)가 있어요. `injection_location`은 `networking.allowed_hosts`와 독립적이에요: `allowed_hosts`는 어떤 호스트에 시크릿이 치환되는지 한정하고, `injection_location`은 요청의 어떤 부분에 치환되는지 한정해요.
`injection_location`은 생성과 업데이트에서 다르게 동작해요:
| 작업 (Operation) | `injection_location` 동작 |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Create credential | 객체를 제공하면 생략한 내부 필드는 기본적으로 `false`가 돼요: `{"header": true}`는 header 전용 자격 증명을 만들어요. 객체를 완전히 생략하면 두 위치가 모두 활성화돼요. |
| Update credential | 필드가 개별적으로 병합돼요: `{"body": false}`는 본문 치환을 비활성화하고 `header`는 그대로 둬요. |
자격 증명은 최소 하나의 위치가 활성화되어야 하므로 두 위치를 모두 비활성화하는 생성이나 업데이트는 400 오류를 반환해요. `injection_location` 객체나 어느 한 필드에 명시적 `null`을 전달해도 400 오류를 반환해요("대신 필드를 생략하세요"). 응답은 항상 해결된 값으로 두 필드를 모두 반환해요.
비활성화된 위치의 플레이스홀더는 치환되지도 제거되지도 않아요. 요청이 그 위치에 리터럴 불투명 플레이스홀더 문자열을 담은 채 타사로 전송돼요. 타사에 리터럴 플레이스홀더 문자열이 담긴 요청이 도착했다면, 그 위치가 자격 증명에 대해 비활성화되었거나 대상 호스트가 자격 증명의 `networking.allowed_hosts`에 포함되지 않았기 때문이에요.
<Note>
Console에서 만든 자격 증명은 헤더 주입만 활성화해요. 클라이언트가 폼 인코딩된 토큰 요청처럼 요청 본문에 시크릿을 보낸다면, 플레이스홀더가 그대로 통과하고 서비스가 자체 인증 오류로 거부해요. 자격 증명을 만들 때 Console 양식에서 본문 주입을 활성화하거나, 자격 증명을 `{"injection_location": {"body": true}}`로 업데이트하세요.
</Note>
치환은 샌드박스 안이 아니라 이그레스(egress)에서 일어나요. 자격 증명을 로컬에서 처리하는 모든 것은 불투명한 플레이스홀더를 보지 실제 값을 보지 못해요: 시작 시 자격 증명 형식을 검증하는 클라이언트는 거부할 수 있고, 시크릿에서 요청 서명을 계산하는 클라이언트(예: AWS SigV4)는 유효하지 않은 서명을 만들어요. 환경 변수 자격 증명은 아웃바운드 요청에 시크릿 값을 그대로, 그리고 자격 증명의 `injection_location`이 활성화하는 위치에 보내는 클라이언트에서 동작해요.
치환은 아웃바운드 전용이에요. 클라이언트가 저장된 시크릿으로 세션 토큰을 가져온다면(예: OAuth client-credentials grant), 반환된 토큰은 샌드박스에 redact되지 않은 채 도착해요. 교환 기반 흐름에서는 교환을 직접 수행하고 결과 토큰을 대신 볼트에 저장하세요.
<Tip>
API 키를 에이전트가 필요한 권한으로만 한정하세요. 에이전트는 키가 허용하는 모든 것을 할 수 있으므로, 필요한 것보다 넓은 권한의 키는 에이전트가 예상치 못하게 행동할 때 폭발 반경을 키워요.
</Tip>
자격 증명은 제공된 대로 저장되고 세션 런타임까지 검증되지 않아요. 유효하지 않은 자격 증명은 세션 도중에 인증 또는 다운스트림 오류로 드러나는데, 이는 발행되지만 세션이 계속되는 것을 막지는 않아요.
제약 사항:
- 볼트당 고유 키.
mcp_server_url(MCP 자격 증명)과secret_name(환경 변수 자격 증명)은 볼트의 활성 자격 증명 사이에서 고유해야 해요. 중복 생성은 409를 반환해요. - 키는 불변.
mcp_server_url이나secret_name을 바꾸려면 자격 증명을 보관하고 새로 만드세요. - 볼트당 최대 20개 자격 증명.
세션 생성 시 볼트 참조하기 (Reference the vault at session creation)
세션을 만들 때 vault_ids를 전달하세요:
ant beta:sessions create \
--agent "$AGENT_ID" \
--environment-id "$ENVIRONMENT_ID" \
--vault-id "$VAULT_ID" \
--title "Alice's Slack digest"
session = client.beta.sessions.create(
agent=agent.id,
environment_id=environment.id,
vault_ids=[vault.id],
title="Alice's Slack digest",
)
const session = await client.beta.sessions.create({
agent: agent.id,
environment_id: environment.id,
vault_ids: [vault.id],
title: "Alice's Slack digest",
});
var session = await client.Beta.Sessions.Create(new()
{
Agent = agent.ID,
EnvironmentID = environment.ID,
VaultIds = [vault.ID],
Title = "Alice's Slack digest",
});
session, err := client.Beta.Sessions.New(ctx, anthropic.BetaSessionNewParams{
Agent: anthropic.BetaSessionNewParamsAgentUnion{
OfString: anthropic.String(agent.ID),
},
EnvironmentID: environment.ID,
VaultIDs: []string{vault.ID},
Title: anthropic.String("Alice's Slack digest"),
})
if err != nil {
panic(err)
}
var session = client.beta().sessions().create(SessionCreateParams.builder()
.agent(agent.id())
.environmentId(environment.id())
.vaultIds(List.of(vault.id()))
.title("Alice's Slack digest")
.build());
$session = $client->beta->sessions->create(
agent: $agent->id,
environmentID: $environment->id,
vaultIDs: [$vault->id],
title: "Alice's Slack digest",
);
session = client.beta.sessions.create(
agent: agent.id,
environment_id: environment.id,
vault_ids: [vault.id],
title: "Alice's Slack digest"
)
런타임 동작:
mcp_server_url로 일치하는 MCP 자격 증명이 없으면 연결이 인증 없이 시도되고, 서버가 인증을 요구하면 오류가 나요.- 여러 볼트가 일치하는 자격 증명을 가질 때, 일치하는 첫 번째 볼트가 이겨요.
- 멀티에이전트 세션에서 볼트 자격 증명은 모든 스레드에 적용돼요. 자체 정의에서 일치하는 MCP 서버를 선언하는 에이전트가 이 자격 증명으로 인증해요. 에이전트를 MCP 서버에 연결하기를 참고하세요.
자격 증명 순환하기 (Rotate a credential)
시크릿 값, display_name, 그리고(환경 변수 자격 증명에선) injection_location은 업데이트할 수 있어요. injection_location 업데이트는 자격 증명 추가하기의 환경 변수 탭에서 설명한 대로 필드별로 병합돼요. 실행 중인 세션의 경우, injection_location 업데이트는 시크릿 순환과 같은 방식으로 전파돼요: 세션의 자격 증명이 자격 증명 수명주기에 설명된 대로 재시작 없이 재해석되고, 업데이트된 위치가 세션의 이후 아웃바운드 요청에 적용돼요. 구조적 필드(mcp_server_url, secret_name, token_endpoint, client_id)는 생성 후 잠겨요. 변경하려면 자격 증명을 보관하고 새로 만드세요.
ant beta:vaults:credentials update \
--vault-id "$VAULT_ID" \
--credential-id "$CREDENTIAL_ID" <<'YAML'
auth:
type: mcp_oauth
access_token: xoxp-new-...
expires_at: "2099-12-31T23:59:59Z"
refresh:
refresh_token: xoxe-1-new-...
YAML
client.beta.vaults.credentials.update(
credential.id,
vault_id=vault.id,
auth={
"type": "mcp_oauth",
"access_token": "xoxp-new-...",
"expires_at": "2099-12-31T23:59:59Z",
"refresh": {"refresh_token": "xoxe-1-new-..."},
},
)
await client.beta.vaults.credentials.update(credential.id, {
vault_id: vault.id,
auth: {
type: "mcp_oauth",
access_token: "xoxp-new-...",
expires_at: "2099-12-31T23:59:59Z",
refresh: {
refresh_token: "xoxe-1-new-...",
},
},
});
await client.Beta.Vaults.Credentials.Update(credential.ID, new()
{
VaultID = vault.ID,
Auth = new BetaManagedAgentsMcpOAuthUpdateParams
{
Type = BetaManagedAgentsMcpOAuthUpdateParamsType.McpOAuth,
AccessToken = "xoxp-new-...",
ExpiresAt = DateTimeOffset.Parse("2099-12-31T23:59:59Z"),
Refresh = new() { RefreshToken = "xoxe-1-new-..." },
},
});
_, err = client.Beta.Vaults.Credentials.Update(ctx, credential.ID, anthropic.BetaVaultCredentialUpdateParams{
VaultID: vault.ID,
Auth: anthropic.BetaVaultCredentialUpdateParamsAuthUnion{
OfMCPOAuth: &anthropic.BetaManagedAgentsMCPOAuthUpdateParams{
Type: anthropic.BetaManagedAgentsMCPOAuthUpdateParamsTypeMCPOAuth,
AccessToken: anthropic.String("xoxp-new-..."),
ExpiresAt: anthropic.Time(time.Date(2099, time.December, 31, 23, 59, 59, 0, time.UTC)),
Refresh: anthropic.BetaManagedAgentsMCPOAuthRefreshUpdateParams{
RefreshToken: anthropic.String("xoxe-1-new-..."),
},
},
},
})
if err != nil {
panic(err)
}
client.beta().vaults().credentials().update(credential.id(),
CredentialUpdateParams.builder()
.vaultId(vault.id())
.auth(BetaManagedAgentsMcpOAuthUpdateParams.builder()
.type(BetaManagedAgentsMcpOAuthUpdateParams.Type.MCP_OAUTH)
.accessToken("xoxp-new-...")
.expiresAt(OffsetDateTime.parse("2099-12-31T23:59:59Z"))
.refresh(BetaManagedAgentsMcpOAuthRefreshUpdateParams.builder()
.refreshToken("xoxe-1-new-...")
.build())
.build())
.build());
$client->beta->vaults->credentials->update(
$credential->id,
vaultID: $vault->id,
auth: ManagedAgentsMCPOAuthUpdateParams::with(
type: 'mcp_oauth',
accessToken: 'xoxp-new-...',
expiresAt: new DateTimeImmutable('2099-12-31T23:59:59Z'),
refresh: ManagedAgentsMCPOAuthRefreshUpdateParams::with(refreshToken: 'xoxe-1-new-...'),
),
);
client.beta.vaults.credentials.update(
credential.id,
vault_id: vault.id,
auth: {
type: "mcp_oauth",
access_token: "xoxp-new-...",
expires_at: "2099-12-31T23:59:59Z",
refresh: {refresh_token: "xoxe-1-new-..."}
}
)
자격 증명 수명주기 (Credential lifecycle)
자격 증명은 세션 도중과 볼트 수명주기 동안 주기적으로 재해석돼요. 이렇게 하면 자격 증명 순환, 보관, 삭제가 재시작 없이 실행 중인 세션으로 전파돼요.
자격 증명이 보관되거나, 삭제되거나, 갱신에 실패하면 그 수명주기 변경과 연결된 볼트·자격 증명 웹훅을 구독해서 알림을 받을 수 있어요.
| 이벤트 (Event) | 트리거 (Trigger) |
|---|---|
vault.archived |
볼트 보관됨. 각 기반 자격 증명에 대해 vault_credential.archived 이벤트도 발행돼요. |
vault.deleted |
볼트 삭제됨. 각 기반 자격 증명에 대해 vault_credential.deleted 이벤트도 발행돼요. |
vault_credential.archived |
자격 증명이 직접 또는 볼트 보관의 결과로 보관됨. |
vault_credential.deleted |
자격 증명이 직접 또는 볼트 삭제의 결과로 삭제됨. |
vault_credential.refresh_failed |
mcp_oauth 자격 증명을 갱신할 수 없음(유효하지 않은 갱신 토큰 또는 OAuth 서버의 복구 불가 오류). |
mcp_oauth 자격 증명의 경우, 재해석은 액세스 토큰이 만료되었으면 그것도 갱신해요. 갱신이 실패하면 vault_credential.refresh_failed 이벤트가 발행돼요.
OAuth 갱신 실패 진단하기 (Diagnose an OAuth refresh failure)
갱신이 실패한 이유를 진단하려면 POST /v1/vaults/{vault_id}/credentials/{credential_id}/mcp_oauth_validate(또는 SDK에서 client.beta.vaults.credentials.mcp_oauth_validate(...))를 호출하세요. 이렇게 하면 실패를 어떻게 처리할지 결정할 수 있어요; 올바른 조치는 오류 유형에 따라 달라져요.
최상위 status가 다음에 무엇을 할지 알려줘요:
valid: 토큰이 동작해요; 조치 불필요.invalid: grant가 사라졌거나 OAuth 서버가 4xx로 갱신을 거부했어요. 최종 사용자에게 재인증을 요청하세요.unknown: 일시적 오류(5xx, 429 또는 네트워크 실패). 기다렸다가 재시도하세요.
ant beta:vaults:credentials mcp-oauth-validate \
--vault-id "$VAULT_ID" \
--credential-id "$CREDENTIAL_ID"
validation = client.beta.vaults.credentials.mcp_oauth_validate(
credential.id,
vault_id=vault.id,
)
print(validation.status) # "valid", "invalid", or "unknown"
const validation = await client.beta.vaults.credentials.mcpOAuthValidate(
credential.id,
{ vault_id: vault.id },
);
console.log(validation.status); // "valid", "invalid", or "unknown"
var validation = await client.Beta.Vaults.Credentials.McpOAuthValidate(credential.ID, new()
{
VaultID = vault.ID,
});
Console.WriteLine(validation.Status.Raw()); // "valid", "invalid", or "unknown"
validation, err := client.Beta.Vaults.Credentials.MCPOAuthValidate(ctx, credential.ID, anthropic.BetaVaultCredentialMCPOAuthValidateParams{
VaultID: vault.ID,
})
if err != nil {
panic(err)
}
fmt.Println(validation.Status) // "valid", "invalid", or "unknown"
var validation = client.beta().vaults().credentials().mcpOAuthValidate(credential.id(),
CredentialMcpOAuthValidateParams.builder()
.vaultId(vault.id())
.build());
IO.println(validation.status()); // valid, invalid, or unknown
$validation = $client->beta->vaults->credentials->mcpOAuthValidate(
$credential->id,
vaultID: $vault->id,
);
echo $validation->status . "\n"; // "valid", "invalid", or "unknown"
validation = client.beta.vaults.credentials.mcp_oauth_validate(
credential.id,
vault_id: vault.id
)
puts validation.status # :valid, :invalid, or :unknown
응답은 vault_credential_validation 객체예요. mcp_probe는 실패한 MCP 핸드셰이크 단계를 포함하고, refresh는 시도된 갱신의 결과를 포함해요.
{
"type": "vault_credential_validation",
"credential_id": "vcrd_01ABC...",
"vault_id": "vlt_01XYZ...",
"validated_at": "2026-04-29T17:12:00Z",
"has_refresh_token": false,
"status": "invalid",
"mcp_probe": {
"method": "initialize",
"http_response": {
"status_code": 401,
"content_type": "application/json",
"body": "{\"error\":\"invalid_token\"}",
"body_truncated": false
}
},
"refresh": {
"status": "no_refresh_token",
"http_response": null
}
}
다른 작업 (Other operations)
- 볼트 또는 자격 증명 나열하기: 페이지네이션되며 최신순이에요. 보관된 기록은 기본적으로 제외돼요(포함하려면
include_archived=true를 전달). - 볼트 보관하기:
POST /v1/vaults/{id}/archive. 모든 자격 증명으로 폭포식 전파돼요. 시크릿은 제거되고, 기록은 감사를 위해 보존돼요. 이 볼트를 참조하는 향후 세션은 실패하고, 실행 중인 세션은 계속돼요. - 자격 증명 보관하기:
POST /v1/vaults/{id}/credentials/{cred_id}/archive. 시크릿 페이로드를 제거하고, 자격 증명 키(mcp_server_url또는secret_name)는 계속 보이며 대체 자격 증명을 위해 풀려요. - 볼트 또는 자격 증명 삭제하기: 하드 삭제. 기록이 보존되지 않아요. 감사 추적이 필요하면 archive를 사용하세요.
더 알아보기 (Learn more)
- 웹훅 구독하기 — 자격 증명 수명주기 이벤트 구독
- 환경 — 환경 수준 네트워킹
- 멀티에이전트 오케스트레이션 — 멀티에이전트 세션의 볼트
- 세션 만들기 — 세션 생성 시 볼트 참조