Best practices
When to use multiple Skills
Combine Skills when tasks involve multiple document types or domains:
Good use cases:
- Data analysis (Excel) + presentation creation (PowerPoint)
- Report generation (Word) + export to PDF
- Custom domain logic + document generation
Avoid:
- Including unused Skills (impacts performance)
Version management strategy
The SDK tabs in this section show the container value to include in a Messages request. The cURL and CLI tabs show the full request.
For production: pin a specific version, so Skill updates never change your deployed behavior. If you omit version or set it to "latest", requests use the newest version of the Skill, so a version uploaded by anyone in the workspace immediately changes what your production agents run. The version ID comes from the create-version response in Versioning or from the List Skill Versions API. The ID is always a string, so quote it in JSON or YAML even when it looks numeric.
# Pin to specific versions for stability
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5",
"max_tokens": 4096,
"container": {
"skills": [{
"type": "custom",
"skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
"version": "skver_01AbCdEfGhIjKlMnOpQrStUv"
}]
},
"messages": [{"role": "user", "content": "Analyze the sales data"}],
"tools": [{"type": "code_execution_20250825", "name": "code_execution"}]
}'# Pin to specific versions for stability
ant messages create <<YAML
model: claude-opus-5
max_tokens: 4096
container:
skills:
- type: custom
skill_id: skill_01AbCdEfGhIjKlMnOpQrStUv
version: "skver_01AbCdEfGhIjKlMnOpQrStUv"
messages:
- role: user
content: Analyze the sales data
tools:
- type: code_execution_20250825
name: code_execution
YAML# Pin to specific versions for stability
container = {
"skills": [
{
"type": "custom",
"skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
"version": "skver_01AbCdEfGhIjKlMnOpQrStUv",
}
]
}// Pin to specific versions for stability
const container: Anthropic.ContainerParams = {
skills: [
{
type: "custom",
skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
version: "skver_01AbCdEfGhIjKlMnOpQrStUv"
}
]
};using Anthropic.Models.Messages;
// Pin to specific versions for stability
var container = new ContainerParams
{
Skills =
[
new SkillParams
{
Type = SkillParamsType.Custom,
SkillID = "skill_01AbCdEfGhIjKlMnOpQrStUv",
Version = "skver_01AbCdEfGhIjKlMnOpQrStUv",
},
],
};// Pin to specific versions for stability
container := anthropic.MessageCreateParamsContainerUnion{
OfContainers: &anthropic.ContainerParams{
Skills: []anthropic.SkillParams{
{
Type: anthropic.SkillParamsTypeCustom,
SkillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
Version: anthropic.String("skver_01AbCdEfGhIjKlMnOpQrStUv"),
},
},
},
}import com.anthropic.models.messages.ContainerParams;
import com.anthropic.models.messages.SkillParams;
void main() {
// Pin to specific versions for stability
ContainerParams container = ContainerParams.builder()
.addSkill(SkillParams.builder()
.type(SkillParams.Type.CUSTOM)
.skillId("skill_01AbCdEfGhIjKlMnOpQrStUv")
.version("skver_01AbCdEfGhIjKlMnOpQrStUv")
.build())
.build();
}// Pin to specific versions for stability
$container = [
'skills' => [[
'type' => 'custom',
'skillID' => 'skill_01AbCdEfGhIjKlMnOpQrStUv',
'version' => 'skver_01AbCdEfGhIjKlMnOpQrStUv'
]]
];# Pin to specific versions for stability
container = {
skills: [{
type: "custom",
skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
version: "skver_01AbCdEfGhIjKlMnOpQrStUv"
}]
}For development: use latest to pick up the newest version automatically as you iterate.
# Use latest for active development
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5",
"max_tokens": 4096,
"container": {
"skills": [{
"type": "custom",
"skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
"version": "latest"
}]
},
"messages": [{"role": "user", "content": "Analyze the sales data"}],
"tools": [{"type": "code_execution_20250825", "name": "code_execution"}]
}'# Use latest for active development
ant messages create <<YAML
model: claude-opus-5
max_tokens: 4096
container:
skills:
- type: custom
skill_id: skill_01AbCdEfGhIjKlMnOpQrStUv
version: latest
messages:
- role: user
content: Analyze the sales data
tools:
- type: code_execution_20250825
name: code_execution
YAML# Use latest for active development
container = {
"skills": [
{
"type": "custom",
"skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
"version": "latest",
}
]
}// Use latest for active development
const container: Anthropic.ContainerParams = {
skills: [
{
type: "custom",
skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
version: "latest"
}
]
};using Anthropic.Models.Messages;
// Use latest for active development
var container = new ContainerParams
{
Skills =
[
new SkillParams
{
Type = SkillParamsType.Custom,
SkillID = "skill_01AbCdEfGhIjKlMnOpQrStUv",
Version = "latest",
},
],
};// Use latest for active development
container := anthropic.MessageCreateParamsContainerUnion{
OfContainers: &anthropic.ContainerParams{
Skills: []anthropic.SkillParams{
{
Type: anthropic.SkillParamsTypeCustom,
SkillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
Version: anthropic.String("latest"),
},
},
},
}import com.anthropic.models.messages.ContainerParams;
import com.anthropic.models.messages.SkillParams;
void main() {
// Use latest for active development
ContainerParams container = ContainerParams.builder()
.addSkill(SkillParams.builder()
.type(SkillParams.Type.CUSTOM)
.skillId("skill_01AbCdEfGhIjKlMnOpQrStUv")
.version("latest")
.build())
.build();
}// Use latest for active development
$container = [
'skills' => [[
'type' => 'custom',
'skillID' => 'skill_01AbCdEfGhIjKlMnOpQrStUv',
'version' => 'latest'
]]
];# Use latest for active development
container = {
skills: [{
type: "custom",
skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
version: "latest"
}]
}Prompt caching considerations
If you use Prompt caching, changing the Skills list in your container breaks the cache. Skills render into the system prompt in a fixed order, so the same list produces the same cacheable prefix:
# Skills render into the system prompt in a fixed, cache-friendly order
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5",
"max_tokens": 4096,
"container": {
"skills": [
{"type": "anthropic", "skill_id": "xlsx", "version": "latest"}
]
},
"messages": [{"role": "user", "content": "Analyze sales data"}],
"tools": [{"type": "code_execution_20250825", "name": "code_execution"}]
}'
# Changing the Skills list ([xlsx] vs [xlsx, pptx]) changes the prefix: a cache miss, while an identical list is a cache hit
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-opus-5",
"max_tokens": 4096,
"container": {
"skills": [
{"type": "anthropic", "skill_id": "xlsx", "version": "latest"},
{"type": "anthropic", "skill_id": "pptx", "version": "latest"}
]
},
"messages": [{"role": "user", "content": "Create a presentation"}],
"tools": [{"type": "code_execution_20250825", "name": "code_execution"}]
}'# Skills render into the system prompt in a fixed, cache-friendly order
ant messages create <<'YAML'
model: claude-opus-5
max_tokens: 4096
container:
skills:
- type: anthropic
skill_id: xlsx
version: latest
messages:
- role: user
content: Analyze sales data
tools:
- type: code_execution_20250825
name: code_execution
YAML
# Changing the Skills list ([xlsx] vs [xlsx, pptx]) changes the prefix: a cache miss, while an identical list is a cache hit
ant messages create <<'YAML'
model: claude-opus-5
max_tokens: 4096
container:
skills:
- type: anthropic
skill_id: xlsx
version: latest
- type: anthropic
skill_id: pptx
version: latest
messages:
- role: user
content: Create a presentation
tools:
- type: code_execution_20250825
name: code_execution
YAMLclient = anthropic.Anthropic()
# Skills render into the system prompt in a fixed, cache-friendly order
response1 = client.messages.create(
model="claude-opus-5",
max_tokens=4096,
container={
"skills": [{"type": "anthropic", "skill_id": "xlsx", "version": "latest"}]
},
messages=[{"role": "user", "content": "Analyze sales data"}],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)
# Changing the Skills list ([xlsx] vs [xlsx, pptx]) changes the prefix: a cache miss, while an identical list is a cache hit
response2 = client.messages.create(
model="claude-opus-5",
max_tokens=4096,
container={
"skills": [
{"type": "anthropic", "skill_id": "xlsx", "version": "latest"},
{
"type": "anthropic",
"skill_id": "pptx",
"version": "latest",
}, # prefix change: cache miss
]
},
messages=[{"role": "user", "content": "Create a presentation"}],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)const client = new Anthropic();
// Skills render into the system prompt in a fixed, cache-friendly order
const response1 = await client.messages.create({
model: "claude-opus-5",
max_tokens: 4096,
container: {
skills: [{ type: "anthropic", skill_id: "xlsx", version: "latest" }]
},
messages: [{ role: "user", content: "Analyze sales data" }],
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
});
// Changing the Skills list ([xlsx] vs [xlsx, pptx]) changes the prefix: a cache miss, while an identical list is a cache hit
const response2 = await client.messages.create({
model: "claude-opus-5",
max_tokens: 4096,
container: {
skills: [
{ type: "anthropic", skill_id: "xlsx", version: "latest" },
{ type: "anthropic", skill_id: "pptx", version: "latest" } // prefix change: cache miss
]
},
messages: [{ role: "user", content: "Create a presentation" }],
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
});AnthropicClient client = new();
// Skills render into the system prompt in a fixed, cache-friendly order
var parameters1 = new MessageCreateParams
{
Model = "claude-opus-5",
MaxTokens = 4096,
Container = new ContainerParams
{
Skills =
[
new SkillParams
{
Type = SkillParamsType.Anthropic,
SkillID = "xlsx",
Version = "latest",
},
],
},
Messages = [new() { Role = Role.User, Content = "Analyze sales data" }],
Tools = [new CodeExecutionTool20250825()],
};
var response1 = await client.Messages.Create(parameters1);
Console.WriteLine(response1);
// Different Skill set ([xlsx] vs [xlsx, pptx]) = a different prefix: a cache miss (an identical set is a cache hit)
var parameters2 = new MessageCreateParams
{
Model = "claude-opus-5",
MaxTokens = 4096,
Container = new ContainerParams
{
Skills =
[
new SkillParams
{
Type = SkillParamsType.Anthropic,
SkillID = "xlsx",
Version = "latest",
},
new SkillParams
{
Type = SkillParamsType.Anthropic,
SkillID = "pptx",
Version = "latest",
},
],
},
Messages = [new() { Role = Role.User, Content = "Create a presentation" }],
Tools = [new CodeExecutionTool20250825()],
};
var response2 = await client.Messages.Create(parameters2);
Console.WriteLine(response2);client := anthropic.NewClient()
// Skills render into the system prompt in a fixed, cache-friendly order
response1, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: "claude-opus-5",
MaxTokens: 4096,
Container: anthropic.MessageCreateParamsContainerUnion{
OfContainers: &anthropic.ContainerParams{
Skills: []anthropic.SkillParams{
{
Type: anthropic.SkillParamsTypeAnthropic,
SkillID: "xlsx",
Version: anthropic.String("latest"),
},
},
},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Analyze sales data")),
},
Tools: []anthropic.ToolUnionParam{
{OfCodeExecutionTool20250825: &anthropic.CodeExecutionTool20250825Param{}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response1)
// Changing the Skills list ([xlsx] vs [xlsx, pptx]) changes the prefix: a cache miss, while an identical list is a cache hit
response2, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: "claude-opus-5",
MaxTokens: 4096,
Container: anthropic.MessageCreateParamsContainerUnion{
OfContainers: &anthropic.ContainerParams{
Skills: []anthropic.SkillParams{
{
Type: anthropic.SkillParamsTypeAnthropic,
SkillID: "xlsx",
Version: anthropic.String("latest"),
},
{
Type: anthropic.SkillParamsTypeAnthropic,
SkillID: "pptx",
Version: anthropic.String("latest"),
},
},
},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Create a presentation")),
},
Tools: []anthropic.ToolUnionParam{
{OfCodeExecutionTool20250825: &anthropic.CodeExecutionTool20250825Param{}},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(response2)import com.anthropic.models.messages.ContainerParams;
import com.anthropic.models.messages.SkillParams;
import com.anthropic.models.messages.CodeExecutionTool20250825;
// ...
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
// Skills render into the system prompt in a fixed, cache-friendly order
MessageCreateParams params1 = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5)
.maxTokens(4096L)
.container(ContainerParams.builder()
.skills(List.of(
SkillParams.builder()
.type(SkillParams.Type.ANTHROPIC)
.skillId("xlsx")
.version("latest")
.build()
))
.build())
.addUserMessage("Analyze sales data")
.addTool(CodeExecutionTool20250825.builder().build())
.build();
Message response1 = client.messages().create(params1);
System.out.println(response1);
// Changing the Skills list ([xlsx] vs [xlsx, pptx]) changes the prefix: a cache miss, while an identical list is a cache hit
MessageCreateParams params2 = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5)
.maxTokens(4096L)
.container(ContainerParams.builder()
.skills(List.of(
SkillParams.builder()
.type(SkillParams.Type.ANTHROPIC)
.skillId("xlsx")
.version("latest")
.build(),
SkillParams.builder()
.type(SkillParams.Type.ANTHROPIC)
.skillId("pptx")
.version("latest")
.build()
))
.build())
.addUserMessage("Create a presentation")
.addTool(CodeExecutionTool20250825.builder().build())
.build();
Message response2 = client.messages().create(params2);
System.out.println(response2);
}$client = new Client();
// Skills render into the system prompt in a fixed, cache-friendly order
$response1 = $client->messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Analyze sales data']
],
model: 'claude-opus-5',
container: [
'skills' => [
['type' => 'anthropic', 'skillID' => 'xlsx', 'version' => 'latest']
]
],
tools: [
['type' => 'code_execution_20250825', 'name' => 'code_execution']
]
);
echo $response1;
// Changing the Skills list ([xlsx] vs [xlsx, pptx]) changes the prefix: a cache miss, while an identical list is a cache hit
$response2 = $client->messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Create a presentation']
],
model: 'claude-opus-5',
container: [
'skills' => [
['type' => 'anthropic', 'skillID' => 'xlsx', 'version' => 'latest'],
['type' => 'anthropic', 'skillID' => 'pptx', 'version' => 'latest']
]
],
tools: [
['type' => 'code_execution_20250825', 'name' => 'code_execution']
]
);
echo $response2;client = Anthropic::Client.new
# Skills render into the system prompt in a fixed, cache-friendly order
response1 = client.messages.create(
model: "claude-opus-5",
max_tokens: 4096,
container: {
skills: [{ type: "anthropic", skill_id: "xlsx", version: "latest" }]
},
messages: [{ role: "user", content: "Analyze sales data" }],
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
)
puts response1
# Changing the Skills list ([xlsx] vs [xlsx, pptx]) changes the prefix: a cache miss, while an identical list is a cache hit
response2 = client.messages.create(
model: "claude-opus-5",
max_tokens: 4096,
container: {
skills: [
{ type: "anthropic", skill_id: "xlsx", version: "latest" },
{ type: "anthropic", skill_id: "pptx", version: "latest" } # prefix change: cache miss
]
},
messages: [{ role: "user", content: "Create a presentation" }],
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
)
puts response2For best caching performance, keep your Skills list, including its order, consistent across requests. Pinning custom Skill versions also helps: with "latest", publishing a new version can invalidate the cached prefix if it changes the Skill's description.
Error handling
Handle Skill-related errors gracefully:
# This error-handling flow doesn't translate well to a one-off shell
# command; one of the SDK options would be a better fit. A failing request
# returns HTTP 400 with an error JSON whose .error.message names the
# Skill problem.if ! RESULT=$(ant messages create \
--transform-error error.message \
--format-error yaml 2>&1 <<'YAML'
model: claude-opus-5
max_tokens: 4096
container:
skills:
- type: custom
skill_id: skill_01AbCdEfGhIjKlMnOpQrStUv
version: latest
messages:
- role: user
content: Process data
tools:
- type: code_execution_20250825
name: code_execution
YAML
); then
case "$RESULT" in
*skill*)
printf 'Skill error: %s\n' "$RESULT"
# Handle skill-specific errors
;;
*)
printf '%s\n' "$RESULT" >&2
exit 1
;;
esac
ficlient = anthropic.Anthropic()
try:
response = client.messages.create(
model="claude-opus-5",
max_tokens=4096,
container={
"skills": [
{
"type": "custom",
"skill_id": "skill_01AbCdEfGhIjKlMnOpQrStUv",
"version": "latest",
}
]
},
messages=[{"role": "user", "content": "Process data"}],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)
except anthropic.BadRequestError as e:
if "skill" in str(e):
print(f"Skill error: {e}")
# Handle skill-specific errors
else:
raiseconst client = new Anthropic();
try {
const response = await client.messages.create({
model: "claude-opus-5",
max_tokens: 4096,
container: {
skills: [
{ type: "custom", skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv", version: "latest" }
]
},
messages: [{ role: "user", content: "Process data" }],
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
});
console.log(response);
} catch (error) {
if (error instanceof Anthropic.BadRequestError && error.message.includes("skill")) {
console.error(`Skill error: ${error.message}`);
// Handle skill-specific errors
} else {
throw error;
}
}using Anthropic.Exceptions;
// ...
AnthropicClient client = new();
try
{
var parameters = new MessageCreateParams
{
Model = "claude-opus-5",
MaxTokens = 4096,
Container = new ContainerParams
{
Skills =
[
new SkillParams
{
Type = SkillParamsType.Custom,
SkillID = "skill_01AbCdEfGhIjKlMnOpQrStUv",
Version = "latest",
},
],
},
Messages = [new() { Role = Role.User, Content = "Process data" }],
Tools = [new CodeExecutionTool20250825()],
};
var response = await client.Messages.Create(parameters);
Console.WriteLine(response);
}
catch (AnthropicBadRequestException e) when (e.Message.Contains("skill"))
{
Console.WriteLine($"Skill error: {e.Message}");
}client := anthropic.NewClient()
response, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
Model: "claude-opus-5",
MaxTokens: 4096,
Container: anthropic.MessageCreateParamsContainerUnion{
OfContainers: &anthropic.ContainerParams{
Skills: []anthropic.SkillParams{
{
Type: anthropic.SkillParamsTypeCustom,
SkillID: "skill_01AbCdEfGhIjKlMnOpQrStUv",
Version: anthropic.String("latest"),
},
},
},
},
Messages: []anthropic.MessageParam{
anthropic.NewUserMessage(anthropic.NewTextBlock("Process data")),
},
Tools: []anthropic.ToolUnionParam{
{OfCodeExecutionTool20250825: &anthropic.CodeExecutionTool20250825Param{}},
},
})
if err != nil {
var apierr *anthropic.Error
if errors.As(err, &apierr) && apierr.Type() == anthropic.ErrorTypeInvalidRequestError &&
strings.Contains(apierr.Error(), "skill") {
fmt.Printf("Skill error: %v\n", apierr)
} else {
log.Fatal(err)
}
return
}
fmt.Println(response)import com.anthropic.errors.BadRequestException;
import com.anthropic.models.messages.ContainerParams;
import com.anthropic.models.messages.SkillParams;
import com.anthropic.models.messages.CodeExecutionTool20250825;
// ...
void main() {
AnthropicClient client = AnthropicOkHttpClient.fromEnv();
try {
MessageCreateParams params = MessageCreateParams.builder()
.model(Model.CLAUDE_OPUS_5)
.maxTokens(4096L)
.container(ContainerParams.builder()
.addSkill(SkillParams.builder()
.type(SkillParams.Type.CUSTOM)
.skillId("skill_01AbCdEfGhIjKlMnOpQrStUv")
.version("latest")
.build())
.build())
.addUserMessage("Process data")
.addTool(CodeExecutionTool20250825.builder().build())
.build();
Message response = client.messages().create(params);
System.out.println(response);
} catch (BadRequestException e) {
if (e.getMessage().contains("skill")) {
System.err.println("Skill error: " + e.getMessage());
} else {
throw e;
}
}
}use Anthropic\Core\Exceptions\BadRequestException;
$client = new Client();
try {
$message = $client->messages->create(
maxTokens: 4096,
messages: [
['role' => 'user', 'content' => 'Process data']
],
model: 'claude-opus-5',
container: [
'skills' => [
[
'type' => 'custom',
'skillID' => 'skill_01AbCdEfGhIjKlMnOpQrStUv',
'version' => 'latest'
]
]
],
tools: [
['type' => 'code_execution_20250825', 'name' => 'code_execution']
]
);
echo $message;
} catch (BadRequestException $e) {
if (str_contains($e->getMessage(), 'skill')) {
echo "Skill error: " . $e->getMessage();
} else {
throw $e;
}
}client = Anthropic::Client.new
begin
response = client.messages.create(
model: "claude-opus-5",
max_tokens: 4096,
container: {
skills: [
{
type: "custom",
skill_id: "skill_01AbCdEfGhIjKlMnOpQrStUv",
version: "latest"
}
]
},
messages: [{ role: "user", content: "Process data" }],
tools: [{ type: "code_execution_20250825", name: "code_execution" }]
)
rescue Anthropic::Errors::BadRequestError => e
if e.message.include?("skill")
puts "Skill error: #{e.message}"
else
raise
end
end