Admin API로 WIF 관리하기

Admin API로 WIF 관리하기

Admin API를 사용하면 Workload Identity Federation 리소스(서비스 계정, 페더레이션 발급자, 페더레이션 규칙)를 프로그래밍 방식으로 만들고 관리할 수 있어요. 페더레이션 구성을 인프라 코드로 유지하고, CI에서 프로비저닝하며, Claude Console을 클릭하지 않고도 조직 간에 재현할 수 있게 도와주지요. 이 엔드포인트들은 나머지 Admin APIhttps://api.anthropic.com/v1/organizations 경로 접두사를 공유해요.

출처: 문서

본문

사전 요구사항

이 페이지의 모든 요청은 org:admin 범위를 지니는 OAuth 베어러 토큰으로 인증해요. 이 범위는 admin, owner, primary owner 역할을 가진 조직 구성원에게만 부여되며, 조직 전체에 접근을 부여해요. 워크스페이스 바인딩은 무시돼요. 토큰을 얻는 방법은 두 가지이며 각각 다른 권한을 지녀요. 내 로그인에서 온 토큰은 사용자로 행동하는 반면, 페더레이션 토큰은 서비스 계정으로 행동하며 이 페이지의 모든 작업을 수행할 수는 없어요.

대화형(내 터미널)

전용 프로필로 ant CLI에 로그인하고 org:admin 범위를 요청한 뒤(Admin access 참고), 베어러 토큰을 내보내세요. --profile admin으로 로그인하면 org:admin 자격 증명을 자체 프로필 이름 아래에 저장하고 CLI의 활성 프로필로도 만들어요. 내보낸 변수는 그 셸의 모든 SDK·CLI 호출에 적용되므로, 관리 전용으로 예약한 셸을 사용하고, 끝나면 변수를 해제하고, ant profile activate default로 CLI를 다시 전환하세요:

ant auth login --profile admin --scope "org:admin"
export ANTHROPIC_AUTH_TOKEN=$(ant auth print-credentials --profile admin --access-token)

대화형 토큰은 수명이 짧아요. 요청이 401을 반환하기 시작하면 export 명령을 다시 실행하세요(자동으로 토큰을 갱신해요).

SDK와 ant CLI는 ANTHROPIC_AUTH_TOKEN을 자동으로 읽어요. 같은 셸에서 ANTHROPIC_API_KEY는 설정하지 않은 채 두세요. 이 엔드포인트들이 API 키를 거부하고, 일부 클라이언트는 둘 다 설정돼 있으면 키를 선호하기 때문이에요.

워크로드(CI와 자동화)

organization_roleadmin인 서비스 계정을 대상으로 하는, oauth_scope: org:admin의 페더레이션 규칙을 만드세요. 규칙 자체는 Claude Console에서 만들어야 해요. 워크로드에 조직 관리자 접근을 부여하는 것은 의도적인 인간의 행동이지, 자동화가 스스로 부트스트랩할 수 있는 게 아니기 때문이에요. 다음 섹션에서 조직당 한 번의 이 설정을 살펴볼게요.

워크로드를 WIF 관리로 부트스트랩하기

Console에서 만든 규칙 하나로 나머지 페더레이션 구성을 인프라 코드 아래에 둘 수 있을 만큼 충분해요. 단일 신뢰 워크로드에 org:admin 범위를 부여하고, 그 워크로드가 이 API를 통해 페더레이션 발급자와 모든 워크스페이스 범위의 페더레이션 규칙을 관리하게 하세요.

  1. Console에서 org:admin 규칙 만들기 Claude Console에서 Settings → Workload identity로 가서 Connect workload를 선택하고 자동화 워크로드(예: 인프라 저장소의 GitHub Actions 워크플로우)에 대한 페더레이션 규칙 하나를 만드세요. Advanced rule options에서 규칙의 OAuth 범위를 org:admin으로 설정하면, 마법사가 Admin 조직 역할로 새 서비스 계정을 만들거나(또는 기존 admin 서비스 계정을 대상으로 선택하라고 요청하거나) org:admin 범위로 규칙을 만들어요.

    경고 규칙을 광범위한 패턴이 아니라 정확히 하나의 워크로드 아이덴티티에 맞추세요. subject_prefix*로 끝나지 않는 한 정확히 일치해요. GitHub Actions의 경우 subject를 보호된 브랜치에 고정하세요(예: repo:my-org/my-repo:ref:refs/heads/main). repo:my-org/my-repo:* 같은 끝자리 와일드카드는 포크에서 트리거된 실행을 포함한 pull_request 실행에도 일치하므로, 저장소에 풀 리퀘스트를 열 수 있는 사람이라면 누구나 org:admin 토큰을 만들 수 있어요. 어떤 워크플로우가 인증할 수 있는지 제한하기를 참고하세요.

  2. 워크로드의 아이덴티티 토큰 교환하기 SDK나 ant CLI 중 하나를 사용하는 워크로드는 교환을 직접 수행하지 않아요. SDK 클라이언트 구성하기의 추론과 정확히 같이, 페더레이션 환경 변수로 클라이언트를 규칙을 가리키게 하고 인자 없이 구성하면 클라이언트가 첫 요청에서 아이덴티티 토큰을 교환하고, 결과 접근 토큰이 만료되기 전에 아이덴티티 토큰을 다시 읽어 다시 교환해요:

    export ANTHROPIC_FEDERATION_RULE_ID=fdrl_...        # 1단계의 org:admin 규칙
    export ANTHROPIC_ORGANIZATION_ID=00000000-0000-0000-0000-000000000000
    export ANTHROPIC_SERVICE_ACCOUNT_ID=svac_...       # 규칙의 대상 서비스 계정
    export ANTHROPIC_IDENTITY_TOKEN_FILE=/path/to/jwt  # 또는 ANTHROPIC_IDENTITY_TOKEN
    # ANTHROPIC_WORKSPACE_ID는 규칙이 모든 워크스페이스 또는 둘 이상에
    # 활성화된 경우에만 필요합니다. org:admin 엔드포인트는 바인딩을 무시합니다.
    unset ANTHROPIC_API_KEY ANTHROPIC_AUTH_TOKEN       # 둘 다 페더레이션보다 우선합니다
    

    ant CLI는 같은 변수를 읽거나 --federation-rule, --organization-id, --service-account-id, --identity-token-file 플래그를 받아요. 둘 이상의 ant 명령을 실행하는 워크로드에는 플래그나 환경 변수보다 페더레이션 프로필을 사용하세요. 플래그나 변수로는 CLI가 매 프로세스마다 아이덴티티 토큰을 다시 교환하는데, jti 클레임을 지니는 아이덴티티 토큰(GitHub Actions 토큰이 그러해요)은 한 번만 수락되어 두 번째 명령은 거부되기 때문이에요. 프로필은 규칙이 모든 워크스페이스 또는 둘 이상에 활성화됐을 때 CLI에 교환용 workspace_id를 주는 유일한 방법이기도 해요. SDK와 달리 CLI는 ANTHROPIC_WORKSPACE_ID--workspace-id를 교환에 전달하지 않거든요. 모든 SDK도 같은 설정을 언어별로 SDK 클라이언트 구성하기에 표시된 대로 명시적 생성자 인자로 받아요. 전체 목록과 순서는 환경 변수자격 증명 우선순위를 참고하세요.

    API를 curl로 호출하는 워크로드는 다른 페더레이션 워크로드와 같은 토큰 교환을 사용해 JWT를 수명이 짧은 org:admin 베어러 토큰으로 스스로 교환하고, 그것을 authorization: Bearer ... 헤더로 보내요.

  3. API를 통해 발급자와 워크스페이스 범위 규칙 관리하기 클라이언트를 구성했으면(또는 curl의 경우 발행된 토큰을 ANTHROPIC_AUTH_TOKEN에 두고), 워크로드는 이 페이지의 엔드포인트를 사용해 페더레이션 구성을 만들고 관리해요.

워크로드 발행 토큰이 수행할 수 있는·없는 작업은 권한과 제약을 참고하세요. Connect workload 마법사로 이미 발급자, 서비스 계정, 규칙을 만들었다면 다음 엔드포인트로 그것들을 나열하고 재생성하는 대신 인프라 코드 상태로 가져오세요.

인증

모든 엔드포인트는 https://api.anthropic.com/v1/organizations/ 아래에 있어요. 페더레이션·서비스 계정 엔드포인트에 대한 모든 요청은 API 버전 헤더와 베어러 토큰이 필요해요:

SDK에서 이 엔드포인트들은 client.beta.organization.service_accounts, client.beta.organization.federation.issuers, client.beta.organization.federation.rules이고(CLI에서는 ant beta:organization:service-accounts, federation:issuers, federation:rules), SDK·CLI 예시는 ANTHROPIC_AUTH_TOKEN에서 베어러 토큰을 보내거나, 자동화 워크로드에서는 WIF 관리 워크로드 부트스트랩에 설명된 대로 페더레이션 교환을 자체 수행하는 기본 클라이언트를 구성해요. SDK 목록 메서드는 필요에 따라 추가 페이지를 가져오므로 limit은 페이지 크기를 설정해요. PHP와 Ruby 예시는 한 페이지를 읽어요.

curl --fail-with-body -sS "https://api.anthropic.com/v1/organizations/service_accounts" \
  -H "authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "anthropic-version: 2023-06-01"
ant beta:organization:service-accounts list
client = anthropic.Anthropic()

service_accounts = client.beta.organization.service_accounts.list()

for service_account in service_accounts:
    print(f"{service_account.id}: {service_account.name}")
const client = new Anthropic();

for await (const serviceAccount of client.beta.organization.serviceAccounts.list()) {
  console.log(`${serviceAccount.id}: ${serviceAccount.name}`);
}
AnthropicClient client = new();

var page = await client.Beta.Organization.ServiceAccounts.List();

await foreach (var serviceAccount in page.Paginate())
{
    Console.WriteLine($"{serviceAccount.ID}: {serviceAccount.Name}");
}
client := anthropic.NewClient()

serviceAccounts := client.Beta.Organization.ServiceAccounts.ListAutoPaging(context.Background(), anthropic.BetaOrganizationServiceAccountListParams{})

for serviceAccounts.Next() {
	serviceAccount := serviceAccounts.Current()
	fmt.Printf("%s: %s\n", serviceAccount.ID, serviceAccount.Name)
}
if err := serviceAccounts.Err(); err != nil {
	log.Fatal(err)
}
AnthropicClient client = AnthropicOkHttpClient.fromEnv();

var serviceAccounts = client.beta().organization().serviceAccounts().list();

for (var serviceAccount : serviceAccounts.autoPager()) {
    IO.println(serviceAccount.id() + ": " + serviceAccount.name());
}
$client = new Client();

$serviceAccounts = $client->beta->organization->serviceAccounts->list();

foreach ($serviceAccounts->getItems() as $serviceAccount) {
    echo "{$serviceAccount->id}: {$serviceAccount->name}\n";
}
client = Anthropic::Client.new

service_accounts = client.beta.organization.service_accounts.list

service_accounts.data.each do |service_account|
  puts "#{service_account.id}: #{service_account.name}"
end

Admin API 키는 이 엔드포인트들에서 수락되지 않아요. Admin API 페이지의 x-api-key 예시는 여기 적용되지 않아요.

서비스 계정(Service accounts)

서비스 계정(svac_...)은 페더레이션 토큰이 행동하는 비인간 아이덴티티예요. organization_roledeveloper로 설정하세요.

서비스 계정 만들기:

curl --fail-with-body -sS "https://api.anthropic.com/v1/organizations/service_accounts" \
  -H "authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "name": "inference-worker",
    "organization_role": "developer"
  }'
ant beta:organization:service-accounts create \
  --name inference-worker \
  --organization-role developer
client = anthropic.Anthropic()

service_account = client.beta.organization.service_accounts.create(
    name="inference-worker", organization_role="developer"
)

print(f"id: {service_account.id}")
print(f"name: {service_account.name}")
const client = new Anthropic();

const serviceAccount = await client.beta.organization.serviceAccounts.create({
  name: "inference-worker",
  organization_role: "developer"
});

console.log(`id: ${serviceAccount.id}`);
console.log(`name: ${serviceAccount.name}`);
using Anthropic.Models.Beta.Organization.ServiceAccounts;

AnthropicClient client = new();

var serviceAccount = await client.Beta.Organization.ServiceAccounts.Create(new()
{
    Name = "inference-worker",
    OrganizationRole = OrganizationRole.Developer
});

Console.WriteLine($"id: {serviceAccount.ID}");
Console.WriteLine($"name: {serviceAccount.Name}");
client := anthropic.NewClient()

serviceAccount, err := client.Beta.Organization.ServiceAccounts.New(context.Background(), anthropic.BetaOrganizationServiceAccountNewParams{
	Name:             "inference-worker",
	OrganizationRole: anthropic.BetaOrganizationServiceAccountNewParamsOrganizationRoleDeveloper,
})
if err != nil {
	log.Fatal(err)
}

fmt.Printf("id: %s\n", serviceAccount.ID)
fmt.Printf("name: %s\n", serviceAccount.Name)
import com.anthropic.models.beta.organization.serviceaccounts.ServiceAccountCreateParams;

void main() {
    AnthropicClient client = AnthropicOkHttpClient.fromEnv();

    var params = ServiceAccountCreateParams.builder()
        .name("inference-worker")
        .organizationRole(ServiceAccountCreateParams.OrganizationRole.DEVELOPER)
        .build();
    var serviceAccount = client.beta().organization().serviceAccounts().create(params);

    IO.println("id: " + serviceAccount.id());
    IO.println("name: " + serviceAccount.name());
}
use Anthropic\Beta\Organization\ServiceAccounts\ServiceAccountCreateParams\OrganizationRole;
// ...

$client = new Client();

$serviceAccount = $client->beta->organization->serviceAccounts->create(
    name: 'inference-worker',
    organizationRole: OrganizationRole::DEVELOPER,
);

echo "id: {$serviceAccount->id}\n";
echo "name: {$serviceAccount->name}\n";
client = Anthropic::Client.new

service_account = client.beta.organization.service_accounts.create(
  name: "inference-worker",
  organization_role: :developer
)

puts "id: #{service_account.id}"
puts "name: #{service_account.name}"

서비스 계정 나열하기:

curl --fail-with-body -sS "https://api.anthropic.com/v1/organizations/service_accounts?limit=20" \
  -H "authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "anthropic-version: 2023-06-01"
ant beta:organization:service-accounts list --limit 20
client = anthropic.Anthropic()

service_accounts = client.beta.organization.service_accounts.list(limit=20)

for service_account in service_accounts:
    print(f"{service_account.id}: {service_account.name}")
const client = new Anthropic();

for await (const serviceAccount of client.beta.organization.serviceAccounts.list({
  limit: 20
})) {
  console.log(`${serviceAccount.id}: ${serviceAccount.name}`);
}
AnthropicClient client = new();

var page = await client.Beta.Organization.ServiceAccounts.List(new() { Limit = 20 });

await foreach (var serviceAccount in page.Paginate())
{
    Console.WriteLine($"{serviceAccount.ID}: {serviceAccount.Name}");
}
client := anthropic.NewClient()

serviceAccounts := client.Beta.Organization.ServiceAccounts.ListAutoPaging(context.Background(), anthropic.BetaOrganizationServiceAccountListParams{
	Limit: anthropic.Int(20),
})

for serviceAccounts.Next() {
	serviceAccount := serviceAccounts.Current()
	fmt.Printf("%s: %s\n", serviceAccount.ID, serviceAccount.Name)
}
if err := serviceAccounts.Err(); err != nil {
	log.Fatal(err)
}
import com.anthropic.models.beta.organization.serviceaccounts.ServiceAccountListParams;

void main() {
    AnthropicClient client = AnthropicOkHttpClient.fromEnv();

    var params = ServiceAccountListParams.builder()
        .limit(20)
        .build();
    var serviceAccounts = client.beta().organization().serviceAccounts().list(params);

    for (var serviceAccount : serviceAccounts.autoPager()) {
        IO.println(serviceAccount.id() + ": " + serviceAccount.name());
    }
}
$client = new Client();

$serviceAccounts = $client->beta->organization->serviceAccounts->list(limit: 20);

foreach ($serviceAccounts->getItems() as $serviceAccount) {
    echo "{$serviceAccount->id}: {$serviceAccount->name}\n";
}
client = Anthropic::Client.new

service_accounts = client.beta.organization.service_accounts.list(limit: 20)

service_accounts.data.each do |service_account|
  puts "#{service_account.id}: #{service_account.name}"
end

서비스 계정 보관하기:

curl --fail-with-body -sS -X POST "https://api.anthropic.com/v1/organizations/service_accounts/svac_01ABCDEFabcdef0123456789XY/archive" \
  -H "authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "anthropic-version: 2023-06-01"
ant beta:organization:service-accounts archive svac_01ABCDEFabcdef0123456789XY
client = anthropic.Anthropic()

service_account = client.beta.organization.service_accounts.archive(
    "svac_01ABCDEFabcdef0123456789XY"
)

print(f"id: {service_account.id}")
print(f"archived_at: {service_account.archived_at}")
const client = new Anthropic();

const serviceAccount = await client.beta.organization.serviceAccounts.archive(
  "svac_01ABCDEFabcdef0123456789XY"
);

console.log(`id: ${serviceAccount.id}`);
console.log(`archived_at: ${serviceAccount.archived_at}`);
AnthropicClient client = new();

var serviceAccount = await client.Beta.Organization.ServiceAccounts.Archive(
    "svac_01ABCDEFabcdef0123456789XY"
);

Console.WriteLine($"id: {serviceAccount.ID}");
Console.WriteLine($"archived_at: {serviceAccount.ArchivedAt:O}");
client := anthropic.NewClient()

serviceAccount, err := client.Beta.Organization.ServiceAccounts.Archive(
	context.Background(),
	"svac_01ABCDEFabcdef0123456789XY",
	anthropic.BetaOrganizationServiceAccountArchiveParams{},
)
if err != nil {
	log.Fatal(err)
}

fmt.Printf("id: %s\n", serviceAccount.ID)
fmt.Printf("archived_at: %s\n", serviceAccount.ArchivedAt)
AnthropicClient client = AnthropicOkHttpClient.fromEnv();

var serviceAccount = client.beta().organization().serviceAccounts()
    .archive("svac_01ABCDEFabcdef0123456789XY");

IO.println("id: " + serviceAccount.id());
IO.println("archived_at: " + serviceAccount.archivedAt().orElseThrow());
$client = new Client();

$serviceAccount = $client->beta->organization->serviceAccounts->archive(
    serviceAccountID: 'svac_01ABCDEFabcdef0123456789XY',
);

echo "id: {$serviceAccount->id}\n";
echo "archived_at: {$serviceAccount->archivedAt?->format(DATE_ATOM)}\n";
client = Anthropic::Client.new

service_account_id = "svac_01ABCDEFabcdef0123456789XY"
service_account = client.beta.organization.service_accounts.archive(service_account_id)

puts "id: #{service_account.id}"
puts "archived_at: #{service_account.archived_at}"

create 엔드포인트는 새 서비스 계정을 반환해요:

{
  "id": "svac_...",
  "name": "inference-worker",
  "organization_role": "developer",
  "created_at": "...",
  "type": "service_account",
  "...": "..."
}

단일 서비스 계정을 읽거나 업데이트하려면 /v1/organizations/service_accounts/{service_account_id}GETPOST를 사용하세요. 서비스 계정은 페더레이션 토큰이 그 안에서 행동할 수 있기 전에 워크스페이스의 구성원이어야 해요. 모든 서비스 계정은 조직의 기본 워크스페이스에 암시적 구성원 자격을 가져요. 다른 워크스페이스에는 /v1/organizations/service_accounts/{service_account_id}/workspacesGET, POST, DELETE로 명시적 구성원 자격을 추가하세요. DELETE.../workspaces/{workspace_id}를 대상으로 해요.

전체 파라미터 세부사항과 응답 스키마는 Service accounts API reference를 참고하세요.

페더레이션 발급자(Federation issuers)

페더레이션 발급자(fdis_...)는 OIDC 아이덴티티 공급자를 조직에 등록해요. jwks 필드는 공급자의 서명 키를 Anthropic이 가져오는 방식을 제어하는 구분된 합집합(discriminated union)이에요:

jwks 사용 시기
{"type": "discovery"} 공급자가 발급자 URL에 /.well-known/openid-configuration을 제공한다.
{"type": "explicit_url", "url": "..."} JWKS 엔드포인트를 직접 가리킨다.
{"type": "inline", "keys": [...]} 공개 인터넷에서 도달할 수 없는 공급자의 키 집합을 업로드한다.

발급자 등록하기. 이 예시는 JWKS discovery로 GitHub Actions를 등록해요:

curl --fail-with-body -sS "https://api.anthropic.com/v1/organizations/federation_issuers" \
  -H "authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "name": "github-actions",
    "issuer_url": "https://token.actions.githubusercontent.com",
    "jwks": {"type": "discovery"}
  }'
ant beta:organization:federation:issuers create \
  --name github-actions \
  --issuer-url https://token.actions.githubusercontent.com \
  --jwks '{type: discovery}'
client = anthropic.Anthropic()

issuer = client.beta.organization.federation.issuers.create(
    name="github-actions",
    issuer_url="https://token.actions.githubusercontent.com",
    jwks={"type": "discovery"},
)

print(f"id: {issuer.id}")
print(f"name: {issuer.name}")
print(f"issuer_url: {issuer.issuer_url}")
const client = new Anthropic();

const issuer = await client.beta.organization.federation.issuers.create({
  name: "github-actions",
  issuer_url: "https://token.actions.githubusercontent.com",
  jwks: { type: "discovery" }
});

console.log(`id: ${issuer.id}`);
console.log(`name: ${issuer.name}`);
console.log(`issuer_url: ${issuer.issuer_url}`);
using Anthropic.Models.Beta.Organization.Federation.Issuers;

AnthropicClient client = new();

var issuer = await client.Beta.Organization.Federation.Issuers.Create(new()
{
    Name = "github-actions",
    IssuerUrl = "https://token.actions.githubusercontent.com",
    Jwks = new BetaJwksDiscovery()
});

Console.WriteLine($"id: {issuer.ID}");
Console.WriteLine($"name: {issuer.Name}");
Console.WriteLine($"issuer_url: {issuer.IssuerUrl}");
client := anthropic.NewClient()

issuer, err := client.Beta.Organization.Federation.Issuers.New(context.Background(), anthropic.BetaOrganizationFederationIssuerNewParams{
	Name:      "github-actions",
	IssuerURL: "https://token.actions.githubusercontent.com",
	JWKS: anthropic.BetaOrganizationFederationIssuerNewParamsJWKSUnion{
		OfDiscovery: &anthropic.BetaJWKSDiscoveryParam{},
	},
})
if err != nil {
	log.Fatal(err)
}

fmt.Printf("id: %s\n", issuer.ID)
fmt.Printf("name: %s\n", issuer.Name)
fmt.Printf("issuer_url: %s\n", issuer.IssuerURL)
import com.anthropic.models.beta.organization.federation.issuers.BetaJwksDiscovery;
import com.anthropic.models.beta.organization.federation.issuers.IssuerCreateParams;

void main() {
    AnthropicClient client = AnthropicOkHttpClient.fromEnv();

    var params = IssuerCreateParams.builder()
        .name("github-actions")
        .issuerUrl("https://token.actions.githubusercontent.com")
        .jwks(BetaJwksDiscovery.builder().build())
        .build();
    var issuer = client.beta().organization().federation().issuers().create(params);

    IO.println("id: " + issuer.id());
    IO.println("name: " + issuer.name());
    IO.println("issuer_url: " + issuer.issuerUrl());
}
$client = new Client();

$issuer = $client->beta->organization->federation->issuers->create(
    name: 'github-actions',
    issuerURL: 'https://token.actions.githubusercontent.com',
    jwks: ['type' => 'discovery'],
);

echo "id: {$issuer->id}\n";
echo "name: {$issuer->name}\n";
echo "issuer_url: {$issuer->issuerURL}\n";
client = Anthropic::Client.new

issuer = client.beta.organization.federation.issuers.create(
  name: "github-actions",
  issuer_url: "https://token.actions.githubusercontent.com",
  jwks: {type: :discovery}
)

puts "id: #{issuer.id}"
puts "name: #{issuer.name}"
puts "issuer_url: #{issuer.issuer_url}"

발급자 나열하기:

curl --fail-with-body -sS "https://api.anthropic.com/v1/organizations/federation_issuers?limit=20" \
  -H "authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "anthropic-version: 2023-06-01"
ant beta:organization:federation:issuers list --limit 20
client = anthropic.Anthropic()

issuers = client.beta.organization.federation.issuers.list(limit=20)

for issuer in issuers:
    print(f"{issuer.id}: {issuer.name}")
const client = new Anthropic();

for await (const issuer of client.beta.organization.federation.issuers.list({ limit: 20 })) {
  console.log(`${issuer.id}: ${issuer.name}`);
}
AnthropicClient client = new();

var page = await client.Beta.Organization.Federation.Issuers.List(new() { Limit = 20 });

await foreach (var issuer in page.Paginate())
{
    Console.WriteLine($"{issuer.ID}: {issuer.Name}");
}
client := anthropic.NewClient()

issuers := client.Beta.Organization.Federation.Issuers.ListAutoPaging(context.Background(), anthropic.BetaOrganizationFederationIssuerListParams{
	Limit: anthropic.Int(20),
})

for issuers.Next() {
	issuer := issuers.Current()
	fmt.Printf("%s: %s\n", issuer.ID, issuer.Name)
}
if err := issuers.Err(); err != nil {
	log.Fatal(err)
}
import com.anthropic.models.beta.organization.federation.issuers.IssuerListParams;

void main() {
    AnthropicClient client = AnthropicOkHttpClient.fromEnv();

    var params = IssuerListParams.builder()
        .limit(20)
        .build();
    var issuers = client.beta().organization().federation().issuers().list(params);

    for (var issuer : issuers.autoPager()) {
        IO.println(issuer.id() + ": " + issuer.name());
    }
}
$client = new Client();

$issuers = $client->beta->organization->federation->issuers->list(limit: 20);

foreach ($issuers->getItems() as $issuer) {
    echo "{$issuer->id}: {$issuer->name}\n";
}
client = Anthropic::Client.new

issuers = client.beta.organization.federation.issuers.list(limit: 20)

issuers.data.each do |issuer|
  puts "#{issuer.id}: #{issuer.name}"
end

발급자 보관하기:

curl --fail-with-body -sS -X POST "https://api.anthropic.com/v1/organizations/federation_issuers/fdis_01ABCDEFabcdef0123456789XY/archive" \
  -H "authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "anthropic-version: 2023-06-01"
ant beta:organization:federation:issuers archive \
  --federation-issuer-id fdis_01ABCDEFabcdef0123456789XY
client = anthropic.Anthropic()

issuer = client.beta.organization.federation.issuers.archive(
    "fdis_01ABCDEFabcdef0123456789XY"
)

print(f"id: {issuer.id}")
print(f"archived_at: {issuer.archived_at}")
const client = new Anthropic();

const issuer = await client.beta.organization.federation.issuers.archive(
  "fdis_01ABCDEFabcdef0123456789XY"
);

console.log(`id: ${issuer.id}`);
console.log(`archived_at: ${issuer.archived_at}`);
AnthropicClient client = new();

var issuer = await client.Beta.Organization.Federation.Issuers.Archive(
    "fdis_01ABCDEFabcdef0123456789XY"
);

Console.WriteLine($"id: {issuer.ID}");
Console.WriteLine($"archived_at: {issuer.ArchivedAt:O}");
client := anthropic.NewClient()

issuer, err := client.Beta.Organization.Federation.Issuers.Archive(
	context.Background(),
	"fdis_01ABCDEFabcdef0123456789XY",
	anthropic.BetaOrganizationFederationIssuerArchiveParams{},
)
if err != nil {
	log.Fatal(err)
}

fmt.Printf("id: %s\n", issuer.ID)
fmt.Printf("archived_at: %s\n", issuer.ArchivedAt)
AnthropicClient client = AnthropicOkHttpClient.fromEnv();

var issuer = client.beta().organization().federation().issuers()
    .archive("fdis_01ABCDEFabcdef0123456789XY");

IO.println("id: " + issuer.id());
IO.println("archived_at: " + issuer.archivedAt().orElseThrow());
$client = new Client();

$issuer = $client->beta->organization->federation->issuers->archive(
    federationIssuerID: 'fdis_01ABCDEFabcdef0123456789XY',
);

echo "id: {$issuer->id}\n";
echo "archived_at: {$issuer->archivedAt?->format(DATE_ATOM)}\n";
client = Anthropic::Client.new

issuer_id = "fdis_01ABCDEFabcdef0123456789XY"
issuer = client.beta.organization.federation.issuers.archive(issuer_id)

puts "id: #{issuer.id}"
puts "archived_at: #{issuer.archived_at}"

단일 발급자를 읽거나 업데이트하려면 /v1/organizations/federation_issuers/{issuer_id}GETPOST를 사용하세요. OAuth 호출자는 oauth_scopeworkspace:developer 또는 workspace:inference가 아닌 규칙의 기반이 되는 발급자를 업데이트할 수 없어요. 권한과 제약을 참고하세요.

전체 파라미터 세부사항과 응답 스키마는 Federation issuers API reference를 참고하세요.

페더레이션 규칙(Federation rules)

페더레이션 규칙(fdrl_...)은 발급자를 서비스 계정에 바인딩해요. 규칙의 일치 조건을 충족하는 발급자의 JWT는 규칙의 대상을 행동하는 토큰을 만들 수 있어요. create 요청의 workspace_id는 생성 시 그 워크스페이스에서 규칙을 활성화해요. 나중에 /federation_rules/{rule_id}/workspaces 하위 리소스로 워크스페이스를 더 추가할 수 있어요. create에는 workspace_id 또는 applies_to_all_workspaces: true 중 하나가 필요해요.

규칙 만들기. 이 예시는 main 브랜치의 GitHub Actions 배포가 서비스 계정으로 행동하게 해요:

curl --fail-with-body -sS "https://api.anthropic.com/v1/organizations/federation_rules" \
  -H "authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{
    "name": "gha-deploy",
    "issuer_id": "fdis_01ABCDEFabcdef0123456789XY",
    "match": {
      "subject_prefix": "repo:my-org/my-repo:ref:refs/heads/main",
      "claims": {"repository_owner": "my-org"}
    },
    "target": {
      "type": "service_account",
      "service_account_id": "svac_01ABCDEFabcdef0123456789XY"
    },
    "workspace_id": "wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ",
    "oauth_scope": "workspace:developer",
    "token_lifetime_seconds": 600
  }'
ant beta:organization:federation:rules create <<'YAML'
name: gha-deploy
issuer_id: fdis_01ABCDEFabcdef0123456789XY
match:
  subject_prefix: "repo:my-org/my-repo:ref:refs/heads/main"
  claims:
    repository_owner: my-org
target:
  type: service_account
  service_account_id: svac_01ABCDEFabcdef0123456789XY
workspace_id: wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ
oauth_scope: "workspace:developer"
token_lifetime_seconds: 600
YAML
client = anthropic.Anthropic()

rule = client.beta.organization.federation.rules.create(
    name="gha-deploy",
    issuer_id="fdis_01ABCDEFabcdef0123456789XY",
    match={
        "subject_prefix": "repo:my-org/my-repo:ref:refs/heads/main",
        "claims": {"repository_owner": "my-org"},
    },
    target={
        "type": "service_account",
        "service_account_id": "svac_01ABCDEFabcdef0123456789XY",
    },
    workspace_id="wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ",
    oauth_scope="workspace:developer",
    token_lifetime_seconds=600,
)

print(f"id: {rule.id}")
print(f"name: {rule.name}")
const client = new Anthropic();

const rule = await client.beta.organization.federation.rules.create({
  name: "gha-deploy",
  issuer_id: "fdis_01ABCDEFabcdef0123456789XY",
  match: {
    subject_prefix: "repo:my-org/my-repo:ref:refs/heads/main",
    claims: { repository_owner: "my-org" }
  },
  target: {
    type: "service_account",
    service_account_id: "svac_01ABCDEFabcdef0123456789XY"
  },
  workspace_id: "wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ",
  oauth_scope: "workspace:developer",
  token_lifetime_seconds: 600
});

console.log(`id: ${rule.id}`);
console.log(`name: ${rule.name}`);
AnthropicClient client = new();

var rule = await client.Beta.Organization.Federation.Rules.Create(new()
{
    Name = "gha-deploy",
    IssuerID = "fdis_01ABCDEFabcdef0123456789XY",
    Match = new()
    {
        SubjectPrefix = "repo:my-org/my-repo:ref:refs/heads/main",
        Claims = new Dictionary<string, string> { ["repository_owner"] = "my-org" }
    },
    Target = new() { ServiceAccountID = "svac_01ABCDEFabcdef0123456789XY" },
    WorkspaceID = "wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ",
    OAuthScope = "workspace:developer",
    TokenLifetimeSeconds = 600
});

Console.WriteLine($"id: {rule.ID}");
Console.WriteLine($"name: {rule.Name}");
client := anthropic.NewClient()

rule, err := client.Beta.Organization.Federation.Rules.New(context.Background(), anthropic.BetaOrganizationFederationRuleNewParams{
	Name:     "gha-deploy",
	IssuerID: "fdis_01ABCDEFabcdef0123456789XY",
	Match: anthropic.BetaFederationRuleMatchParam{
		SubjectPrefix: anthropic.String("repo:my-org/my-repo:ref:refs/heads/main"),
		Claims:        map[string]string{"repository_owner": "my-org"},
	},
	Target: anthropic.BetaServiceAccountTargetParam{
		ServiceAccountID: "svac_01ABCDEFabcdef0123456789XY",
	},
	WorkspaceID:          anthropic.String("wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ"),
	OAuthScope:           "workspace:developer",
	TokenLifetimeSeconds: anthropic.Int(600),
})
if err != nil {
	log.Fatal(err)
}

fmt.Printf("id: %s\n", rule.ID)
fmt.Printf("name: %s\n", rule.Name)
import com.anthropic.core.JsonValue;
import com.anthropic.models.beta.organization.federation.rules.BetaFederationRuleMatch;
import com.anthropic.models.beta.organization.federation.rules.BetaServiceAccountTarget;
import com.anthropic.models.beta.organization.federation.rules.RuleCreateParams;

void main() {
    AnthropicClient client = AnthropicOkHttpClient.fromEnv();

    var match = BetaFederationRuleMatch.builder()
        .subjectPrefix("repo:my-org/my-repo:ref:refs/heads/main")
        .claims(BetaFederationRuleMatch.Claims.builder()
            .putAdditionalProperty("repository_owner", JsonValue.from("my-org"))
            .build())
        .build();
    var params = RuleCreateParams.builder()
        .name("gha-deploy")
        .issuerId("fdis_01ABCDEFabcdef0123456789XY")
        .match(match)
        .target(BetaServiceAccountTarget.of("svac_01ABCDEFabcdef0123456789XY"))
        .workspaceId("wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ")
        .oauthScope("workspace:developer")
        .tokenLifetimeSeconds(600)
        .build();
    var rule = client.beta().organization().federation().rules().create(params);

    IO.println("id: " + rule.id());
    IO.println("name: " + rule.name());
}
$client = new Client();

$rule = $client->beta->organization->federation->rules->create(
    name: 'gha-deploy',
    issuerID: 'fdis_01ABCDEFabcdef0123456789XY',
    match: [
        'subjectPrefix' => 'repo:my-org/my-repo:ref:refs/heads/main',
        'claims' => ['repository_owner' => 'my-org'],
    ],
    target: [
        'type' => 'service_account',
        'serviceAccountID' => 'svac_01ABCDEFabcdef0123456789XY',
    ],
    workspaceID: 'wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ',
    oauthScope: 'workspace:developer',
    tokenLifetimeSeconds: 600,
);

echo "id: {$rule->id}\n";
echo "name: {$rule->name}\n";
client = Anthropic::Client.new

rule = client.beta.organization.federation.rules.create(
  name: "gha-deploy",
  issuer_id: "fdis_01ABCDEFabcdef0123456789XY",
  match: {
    subject_prefix: "repo:my-org/my-repo:ref:refs/heads/main",
    claims: {repository_owner: "my-org"}
  },
  target: {
    type: :service_account,
    service_account_id: "svac_01ABCDEFabcdef0123456789XY"
  },
  workspace_id: "wrkspc_01JwQvzr7rXLA5AGx3HKfFUJ",
  oauth_scope: "workspace:developer",
  token_lifetime_seconds: 600
)

puts "id: #{rule.id}"
puts "name: #{rule.name}"

규칙 나열하기, 선택적으로 발급자로 필터링:

curl --fail-with-body -sS "https://api.anthropic.com/v1/organizations/federation_rules?issuer_id=fdis_01ABCDEFabcdef0123456789XY" \
  -H "authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "anthropic-version: 2023-06-01"
ant beta:organization:federation:rules list \
  --issuer-id fdis_01ABCDEFabcdef0123456789XY
client = anthropic.Anthropic()

rules = client.beta.organization.federation.rules.list(
    issuer_id="fdis_01ABCDEFabcdef0123456789XY"
)

for rule in rules:
    print(f"{rule.id}: {rule.name}")
const client = new Anthropic();

for await (const rule of client.beta.organization.federation.rules.list({
  issuer_id: "fdis_01ABCDEFabcdef0123456789XY"
})) {
  console.log(`${rule.id}: ${rule.name}`);
}
AnthropicClient client = new();

var page = await client.Beta.Organization.Federation.Rules.List(new()
{
    IssuerID = "fdis_01ABCDEFabcdef0123456789XY"
});

await foreach (var rule in page.Paginate())
{
    Console.WriteLine($"{rule.ID}: {rule.Name}");
}
client := anthropic.NewClient()

rules := client.Beta.Organization.Federation.Rules.ListAutoPaging(context.Background(), anthropic.BetaOrganizationFederationRuleListParams{
	IssuerID: anthropic.String("fdis_01ABCDEFabcdef0123456789XY"),
})

for rules.Next() {
	rule := rules.Current()
	fmt.Printf("%s: %s\n", rule.ID, rule.Name)
}
if err := rules.Err(); err != nil {
	log.Fatal(err)
}
import com.anthropic.models.beta.organization.federation.rules.RuleListParams;

void main() {
    AnthropicClient client = AnthropicOkHttpClient.fromEnv();

    var params = RuleListParams.builder()
        .issuerId("fdis_01ABCDEFabcdef0123456789XY")
        .build();
    var rules = client.beta().organization().federation().rules().list(params);

    for (var rule : rules.autoPager()) {
        IO.println(rule.id() + ": " + rule.name());
    }
}
$client = new Client();

$rules = $client->beta->organization->federation->rules->list(
    issuerID: 'fdis_01ABCDEFabcdef0123456789XY',
);

foreach ($rules->getItems() as $rule) {
    echo "{$rule->id}: {$rule->name}\n";
}
client = Anthropic::Client.new

rules = client.beta.organization.federation.rules.list(
  issuer_id: "fdis_01ABCDEFabcdef0123456789XY"
)

rules.data.each do |rule|
  puts "#{rule.id}: #{rule.name}"
end

규칙 보관하기:

curl --fail-with-body -sS -X POST "https://api.anthropic.com/v1/organizations/federation_rules/fdrl_01ABCDEFabcdef0123456789XY/archive" \
  -H "authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "anthropic-version: 2023-06-01"
ant beta:organization:federation:rules archive \
  --federation-rule-id fdrl_01ABCDEFabcdef0123456789XY
client = anthropic.Anthropic()

rule = client.beta.organization.federation.rules.archive(
    "fdrl_01ABCDEFabcdef0123456789XY"
)

print(f"id: {rule.id}")
print(f"archived_at: {rule.archived_at}")
const client = new Anthropic();

const rule = await client.beta.organization.federation.rules.archive(
  "fdrl_01ABCDEFabcdef0123456789XY"
);

console.log(`id: ${rule.id}`);
console.log(`archived_at: ${rule.archived_at}`);
AnthropicClient client = new();

var rule = await client.Beta.Organization.Federation.Rules.Archive(
    "fdrl_01ABCDEFabcdef0123456789XY"
);

Console.WriteLine($"id: {rule.ID}");
Console.WriteLine($"archived_at: {rule.ArchivedAt:O}");
client := anthropic.NewClient()

rule, err := client.Beta.Organization.Federation.Rules.Archive(
	context.Background(),
	"fdrl_01ABCDEFabcdef0123456789XY",
	anthropic.BetaOrganizationFederationRuleArchiveParams{},
)
if err != nil {
	log.Fatal(err)
}

fmt.Printf("id: %s\n", rule.ID)
fmt.Printf("archived_at: %s\n", rule.ArchivedAt)
AnthropicClient client = AnthropicOkHttpClient.fromEnv();

var rule = client.beta().organization().federation().rules()
    .archive("fdrl_01ABCDEFabcdef0123456789XY");

IO.println("id: " + rule.id());
IO.println("archived_at: " + rule.archivedAt().orElseThrow());
$client = new Client();

$rule = $client->beta->organization->federation->rules->archive(
    federationRuleID: 'fdrl_01ABCDEFabcdef0123456789XY',
);

echo "id: {$rule->id}\n";
echo "archived_at: {$rule->archivedAt?->format(DATE_ATOM)}\n";
client = Anthropic::Client.new

rule_id = "fdrl_01ABCDEFabcdef0123456789XY"
rule = client.beta.organization.federation.rules.archive(rule_id)

puts "id: #{rule.id}"
puts "archived_at: #{rule.archived_at}"

list 엔드포인트는 규칙 페이지와 다음 페이지 커서를 반환해요:

{
  "data": [{ "id": "fdrl_...", "name": "gha-deploy", "...": "..." }],
  "next_page": "..."
}

단일 규칙을 읽거나 업데이트하려면 /v1/organizations/federation_rules/{rule_id}GETPOST를 사용하세요. 규칙이 토큰을 만들 수 있는 워크스페이스를 관리하려면 /v1/organizations/federation_rules/{rule_id}/workspacesGETPOST를, /v1/organizations/federation_rules/{rule_id}/workspaces/{workspace_id}DELETE를 사용하세요.

전체 파라미터 세부사항과 응답 스키마는 Federation rules API reference를 참고하세요.

권한과 제약

참고

  • OAuth 인증 호출자는 oauth_scopeworkspace:developer 또는 workspace:inference인 규칙만 만들거나 수정할 수 있어요. 다른 범위(예: org:admin 또는 workspace:manage_tunnels)의 규칙을 만들거나 수정하려면 Console을 사용하세요.
  • OAuth 호출자는 oauth_scopeworkspace:developer 또는 workspace:inference가 아닌(예: org:admin 또는 workspace:manage_tunnels) 규칙의 기반이 되는 페더레이션 발급자를 업데이트할 수 없어요. 부트스트랩 규칙용 전용 발급자를 등록해서 워크스페이스 범위 규칙 뒤의 발급자가 API로 업데이트 가능하게 유지하는 것을 고려하세요.
  • Admin API 키는 읽기든 쓰기든 이 엔드포인트들에서 수락되지 않아요. org:admin OAuth 토큰을 사용하세요.

oauth_scope: org:admin 규칙은 organization_roleadmin인 서비스 계정을 대상으로 해야 해요. 리소스 이름은 ^[a-z0-9-]+$와 일치해야 하고, 1~255자여야 하며, 리소스 타입별로 조직 내에서 고유해야 해요. 전체 필드 수준 제약은 검증 규칙을 참고하세요.

페이지네이션과 보관

서비스 계정, 페더레이션 발급자, 페더레이션 규칙 list 엔드포인트는 limit(1~100, 기본 20)과 이전 응답에서 가져온 page 커서를 받아요. 응답의 next_page 값을 다음 요청의 page 쿼리 파라미터로 전달하세요. 규칙-워크스페이스 하위 리소스 목록은 페이지네이션 없이 전체 집합을 반환해요. 보관된 리소스는 기본적으로 목록에서 숨겨져요. 포함하려면 include_archived=true를 전달하세요.

보관은 부드러운 삭제이며 멱등적이에요. 이미 보관된 리소스를 보관하면 성공해요. 발급자나 서비스 계정을 살아있는 페더레이션 규칙이 여전히 참조하는 동안 보관하면 400을 반환해요. 먼저 규칙을 보관하세요.

함께 보기

더 알아보기 (Learn more)