Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions .claude/skills/claude-platform-on-aws/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
name: claude-platform-on-aws
description: Convert Anthropic 1P (direct API) code samples to Claude Platform on AWS (CPOA). Use when migrating from api.anthropic.com to aws-external-anthropic.{region}.api.aws, converting auth to IAM SigV4 or CPOA API key, adding workspace headers, adapting self-hosted sandbox workers for CPOA, or when someone asks about differences between Anthropic 1P and CPOA authentication/endpoints. Triggers on phrases like "convert to CPOA", "migrate to Claude Platform on AWS", "CPOA auth", "workspace header", "aws-external-anthropic", "SigV4 anthropic".
---

# Claude Platform on AWS — Migration Skill

Convert Anthropic 1P API code to Claude Platform on AWS (CPOA).

## Key Differences: 1P vs CPOA

| Aspect | Anthropic 1P | CPOA |
|--------|-------------|------|
| Base URL | `https://api.anthropic.com` | `https://aws-external-anthropic.{region}.api.aws` |
| Auth | API key (`sk-ant-api03-...`) in `x-api-key` header | IAM SigV4 OR CPOA API key (generated in AWS Console) |
| Required headers | `x-api-key`, `anthropic-version` | `anthropic-workspace-id` (handled by platform SDK clients) |
| SDK client | `Anthropic` | `AnthropicAWS` (Python) / `AnthropicAws` (TypeScript) |
| Billing | Anthropic billing | AWS Marketplace |
| Inference | Anthropic infra | Anthropic infra (same — CPOA only changes auth/billing layer) |
| Env keys (self-hosted workers) | `sk-ant-oat01-...` (from Claude Console) | NOT used — workers authenticate via IAM or CPOA API key |
| VPC access | Direct HTTPS to api.anthropic.com | PrivateLink available (VPC→AWS hop only; inference still on Anthropic) |
| Credential rotation | Manual API key management | Automatic via STS (IAM mode) |

## Conversion Steps

### 1. Identify Auth Mode

CPOA supports two auth modes:

- **IAM SigV4** (preferred for production): Uses execution role credentials via standard AWS provider chain. No secrets to manage.
- **API key** (dev/quick-start): Generated in AWS Console under Claude Platform on AWS → API keys. Short-term tokens generated from AWS credentials default to 12 hours (capped at the lesser of requested duration, AWS credentials' expiry, and 12 hours).

### 2. Install the Platform SDK

The platform-specific SDK client handles SigV4 signing, base URL construction, and the `anthropic-workspace-id` header automatically.

```bash
# Python
pip install -U "anthropic[aws]"

# TypeScript
npm install @anthropic-ai/aws-sdk
```

### 3. Convert Client Initialization

See `references/code-patterns.md` for language-specific conversion patterns (Python, Node.js, curl).

The platform client reads `AWS_REGION` and `ANTHROPIC_AWS_WORKSPACE_ID` from environment variables. Set them:

```bash
export AWS_REGION='us-west-2'
export ANTHROPIC_AWS_WORKSPACE_ID='wrkspc_XXXXX'
```

### 4. Update Self-Hosted Sandbox Workers

For self-hosted environments on CPOA:
- Environment keys from the Claude Console **do not work** on the CPOA endpoint
- Workers authenticate via IAM (attach `AnthropicSelfHostedEnvironmentAccess` policy) or CPOA API key
- Use the `EnvironmentWorker` SDK helper (from `anthropic.lib.environments`) or call `GET /v1/environments/{id}/work/poll` directly

### 5. IAM Permissions

Different policies for different use cases:

- **Inference** (`/v1/messages`): Attach `AnthropicInferenceAccess` managed policy (includes `CreateInference`)
- **Self-hosted workers**: Attach `AnthropicSelfHostedEnvironmentAccess` managed policy (includes `ProcessEnvironmentWork`, `GetEnvironment`, `GetSession`, `UpdateSession`, `GetSkill`, `CallWithBearerToken` — but NOT `CreateInference`)

A role with only `AnthropicSelfHostedEnvironmentAccess` will 403 on `/v1/messages`. Combine both policies if the worker also needs to make inference calls.

### 6. Security Comparison

CPOA advantages for sandboxed/multi-tenant environments:
- No long-lived secrets — IAM creds rotate hourly via STS
- IAM policy scoping — restrict to specific actions
- Condition keys — `aws:SourceVpc`, `aws:SourceAccount`
- CloudTrail audit — native AWS logging
- SCP enforcement at org level

Tradeoff: more operational complexity (IAM setup, workspace subscription, outbound web identity federation enablement).

## Common Pitfalls

1. **Missing workspace header** → 400 error. Use the platform SDK client (`AnthropicAWS`/`AnthropicAws`) which adds it automatically, or set `ANTHROPIC_AWS_WORKSPACE_ID` env var.
2. **Outbound web identity federation disabled** → Every request fails with "Outbound web identity federation is disabled for your account". Run `aws iam enable-outbound-web-identity-federation` once per AWS account.
3. **Using Claude Console environment keys on CPOA** → Won't work. Workers on CPOA authenticate via IAM or CPOA API key instead.
4. **Confusing IAM policies** → `AnthropicSelfHostedEnvironmentAccess` does NOT grant inference. Need `AnthropicInferenceAccess` for `/v1/messages`.
5. **Region not set** → Unlike `AnthropicBedrock` (which falls back to `us-east-1`), the platform client raises an error if no region is set.
6. **Account not CPOA-subscribed** → SigV4 will fail; ensure the AWS account has completed Claude Platform on AWS sign-up.
286 changes: 286 additions & 0 deletions .claude/skills/claude-platform-on-aws/references/code-patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
# Code Patterns: 1P → CPOA Conversion

## Python

### Before (Anthropic 1P)

```python
from anthropic import Anthropic

client = Anthropic(api_key="sk-ant-api03-...")

response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
```

### After (CPOA — API Key Mode)

```python
from anthropic import AnthropicAWS # pip install -U "anthropic[aws]"

# API key generated in AWS Console under Claude Platform on AWS → API keys
# Region + workspace from AWS_REGION / ANTHROPIC_AWS_WORKSPACE_ID env vars
client = AnthropicAWS(api_key="<your-cpoa-api-key>")

response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
```

### After (CPOA — SigV4 IAM Mode)

```python
from anthropic import AnthropicAWS # pip install -U "anthropic[aws]"

# No API key → SigV4 via default AWS credential chain
# (env → ~/.aws → IRSA → ECS → IMDS)
client = AnthropicAWS(aws_region="us-west-2")

response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
```

### After (CPOA — Short-Term Token)

```python
from token_generator_for_aws_external_anthropic import TokenGenerator
from anthropic import AnthropicAWS

# Token defaults to 12h, capped at min(requested, AWS creds expiry, 12h)
token = TokenGenerator(region="us-west-2").get_token()

client = AnthropicAWS(api_key=token, aws_region="us-west-2")

response = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
```

## Node.js / TypeScript

### Before (Anthropic 1P)

```typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic({ apiKey: "sk-ant-api03-..." });

const response = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello" }],
});
```

### After (CPOA — API Key Mode)

```typescript
import AnthropicAws from "@anthropic-ai/aws-sdk"; // npm install @anthropic-ai/aws-sdk

// API key generated in AWS Console under Claude Platform on AWS → API keys
const client = new AnthropicAws({
apiKey: "<your-cpoa-api-key>",
awsRegion: "us-west-2",
});

const response = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello" }],
});
```

### After (CPOA — SigV4 IAM Mode)

```typescript
import AnthropicAws from "@anthropic-ai/aws-sdk";

// No API key → SigV4 via default AWS credential chain
const client = new AnthropicAws({ awsRegion: "us-west-2" });

const response = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello" }],
});
```

### After (CPOA — Short-Term Token)

```typescript
import { getTokenProvider } from "@aws/token-generator-for-aws-external-anthropic";
import AnthropicAws from "@anthropic-ai/aws-sdk";

const tokenProvider = getTokenProvider({ region: "us-west-2" });
const token = await tokenProvider();

const client = new AnthropicAws({ apiKey: token, awsRegion: "us-west-2" });

const response = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello" }],
});
```

## curl

### Before (Anthropic 1P)

```bash
curl -X POST https://api.anthropic.com/v1/messages \
-H "x-api-key: sk-ant-api03-..." \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model":"claude-sonnet-5","max_tokens":1024,"messages":[{"role":"user","content":"Hello"}]}'
```

### After (CPOA — API Key Mode)

```bash
curl -X POST https://aws-external-anthropic.us-west-2.api.aws/v1/messages \
-H "x-api-key: <your-cpoa-api-key>" \
-H "anthropic-workspace-id: wrkspc_XXXXX" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model":"claude-sonnet-5","max_tokens":1024,"messages":[{"role":"user","content":"Hello"}]}'
```

### After (CPOA — SigV4 Mode)

```bash
# Uses curl's --aws-sigv4 flag (requires curl 7.75+)
curl -X POST "https://aws-external-anthropic.us-west-2.api.aws/v1/messages" \
--aws-sigv4 "aws:amz:us-west-2:aws-external-anthropic" \
--user "$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY" \
-H "x-amz-security-token: $AWS_SESSION_TOKEN" \
-H "anthropic-workspace-id: $ANTHROPIC_AWS_WORKSPACE_ID" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model":"claude-sonnet-5","max_tokens":1024,"messages":[{"role":"user","content":"Hello"}]}'
```

## Self-Hosted Sandbox Workers

### Before (1P — Environment Key)

```python
import asyncio
import os
from anthropic import AsyncAnthropic
from anthropic.lib.environments import EnvironmentWorker

async def main() -> None:
environment_key = os.environ["ANTHROPIC_ENVIRONMENT_KEY"] # sk-ant-oat01-...
environment_id = os.environ["ANTHROPIC_ENVIRONMENT_ID"]
async with AsyncAnthropic(auth_token=environment_key) as client:
await EnvironmentWorker(
client,
environment_id=environment_id,
environment_key=environment_key,
workdir="/workspace",
).run()

asyncio.run(main())
```

### After (CPOA — Worker with IAM Auth)

```python
import asyncio
import os
from anthropic import AsyncAnthropic
from anthropic.lib.environments import EnvironmentWorker

async def main() -> None:
# On CPOA: no environment key needed — authenticate via IAM
# Requires AnthropicSelfHostedEnvironmentAccess IAM policy
environment_id = os.environ["ANTHROPIC_ENVIRONMENT_ID"]
async with AsyncAnthropic() as client: # SigV4 via AWS credential chain

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing that slipped past the earlier round: the "Worker with IAM Auth" block still uses bare AsyncAnthropic(), and the comment # SigV4 via AWS credential chain is inaccurate — base AsyncAnthropic targets api.anthropic.com and can't SigV4-sign (same defect we fixed for the inference examples at :49/:109). We corrected the prose for this in SKILL.md:19 but not this code block.

Two things to reconcile:

  1. Use the AWS client, not the base one — the CPOA worker should be constructed via the platform client that signs and sets the workspace header.
  2. If a SigV4-authenticated EnvironmentWorker isn't actually a documented pattern (the self-hosted docs only show the async worker with auth_token=<key>), mark this variant as unverified rather than shipping it as working. The API-key worker block just below is fine as-is.

await EnvironmentWorker(
client,
environment_id=environment_id,
workdir="/workspace",
).run()

asyncio.run(main())
```

### After (CPOA — Worker with API Key)

```python
import asyncio
import os
from anthropic import AsyncAnthropic
from anthropic.lib.environments import EnvironmentWorker

async def main() -> None:
# On CPOA: use API key generated in AWS Console
api_key = os.environ["ANTHROPIC_AWS_API_KEY"]
environment_id = os.environ["ANTHROPIC_ENVIRONMENT_ID"]
async with AsyncAnthropic(auth_token=api_key) as client:
await EnvironmentWorker(
client,
environment_id=environment_id,
workdir="/workspace",
).run()

asyncio.run(main())
```

### Session Creation (CPOA)

```python
from anthropic import AnthropicAWS

client = AnthropicAWS(aws_region="us-west-2")

# Create session targeting an agent in a self-hosted environment
session = client.beta.sessions.create(
agent="agent_XXXXX",
environment_id="env_XXXXX",
)

# Send user message
client.beta.sessions.events.send(
session_id=session.id,
events=[{
"type": "user.message",
"content": [{"type": "text", "text": "Analyze the data in /mnt/data/"}],
}],
)

# Session transitions: idle → running (work queued for worker pickup)
```

## Environment Variables Mapping

| 1P Variable | CPOA Variable | Notes |
|-------------|---------------|-------|
| `ANTHROPIC_API_KEY` | (constructor `api_key`) | CPOA keys generated in AWS Console |
| (none) | `ANTHROPIC_AWS_WORKSPACE_ID` | Required — SDK reads automatically |
| (none) | `AWS_REGION` | Required — no fallback default |
| `ANTHROPIC_BASE_URL` | (handled by SDK) | `AnthropicAWS`/`AnthropicAws` sets it from region |
| `ANTHROPIC_ENVIRONMENT_KEY` | (not used on CPOA) | 1P uses `sk-ant-oat01-...`; CPOA workers use IAM or API key instead |

## Validation Checklist

After conversion, verify:

1. [ ] Using platform SDK client (`AnthropicAWS` / `AnthropicAws`), not base `Anthropic` with manual URL
2. [ ] `AWS_REGION` and `ANTHROPIC_AWS_WORKSPACE_ID` environment variables set
3. [ ] No references to `sk-ant-api03-` or `sk-ant-oat01-` keys (1P credentials)
4. [ ] Outbound web identity federation enabled (`aws iam enable-outbound-web-identity-federation`)
5. [ ] Correct IAM policy: `AnthropicInferenceAccess` for `/v1/messages`, `AnthropicSelfHostedEnvironmentAccess` for workers
6. [ ] `/v1/models` endpoint returns 200 (basic connectivity test)
7. [ ] Short-term token refresh logic in place if using generated tokens (no auto-refresh)