| title | Agent Skill |
|---|---|
| description | Store and manage reusable agent skills for AI-powered workflows |
The Skills API provides storage and management for Agent Skills - a simple, open format for giving agents new capabilities and expertise. Skills are folders of instructions, scripts, and resources that agents can discover and use to perform tasks more accurately and efficiently.
Mount skills in a sandbox to execute skill scripts and access skill files. Enable your agent to read skill files with pre-built tools. No sandbox required.If you'd like to know the details of Skills APIs, please read the following documentation.
In this quickstart, you'll learn how to:
- Upload a skill from a ZIP file
- Browse the skill catalog
- Retrieve skill metadata and file index
- Read files from a skill
- Delete skills when no longer needed
Before you begin, ensure you have:
- An Acontext API key from Acontext Dashboard, or local deployment
- A skill ZIP file to upload
A valid skill ZIP file must contain a SKILL.md file at the root with YAML frontmatter containing name and description:
---
name: data-extraction
description: Extract structured data from documents using various techniques
---
# Data Extraction Skill
This skill helps agents extract structured data from unstructured documents...The ZIP can contain any additional files the skill needs: scripts, templates, examples, configuration files, etc.
First, create a client instance with your API key and base URL.
```python Python import os from acontext import AcontextClientclient = AcontextClient( api_key=os.getenv("ACONTEXT_API_KEY"), )
base_url="http://localhost:8029/api/v1",
client.ping()
```typescript TypeScript
import { AcontextClient } from '@acontext/acontext';
const client = new AcontextClient({
apiKey: process.env.ACONTEXT_API_KEY,
});
// If you're using self-hosted Acontext:
// const client = new AcontextClient({
// baseUrl: 'http://localhost:8029/api/v1',
// apiKey: 'sk-ac-your-root-api-bearer-token',
// });
with open("data-extraction-skill.zip", "rb") as f: zip_content = f.read()
skill = client.skills.create( file=FileUpload( filename="data-extraction-skill.zip", content=zip_content ), user="user@example.com", meta={"category": "data-processing", "version": "1.0"} )
print(f"Created skill: {skill.name}") print(f"Skill ID: {skill.id}") print(f"Description: {skill.description}")
```typescript TypeScript
import { FileUpload } from '@acontext/acontext';
import * as fs from 'fs';
// Read your skill ZIP file
const zipContent = fs.readFileSync('data-extraction-skill.zip');
// Upload the skill
const skill = await client.skills.create({
file: new FileUpload({
filename: 'data-extraction-skill.zip',
content: zipContent,
contentType: 'application/zip'
}),
user: 'user@example.com',
meta: { category: 'data-processing', version: '1.0' }
});
console.log(`Created skill: ${skill.name}`);
console.log(`Skill ID: ${skill.id}`);
console.log(`Description: ${skill.description}`);
The skill object contains:
id: Unique skill identifier (UUID)name: Skill name from SKILL.mddescription: Skill description from SKILL.mdfile_index: List of files with paths and MIME typesmeta: Your custom metadatacreated_at: ISO 8601 timestampupdated_at: ISO 8601 timestamp
print("Available skills:") for item in catalog.items: print(f" - {item.name}: {item.description}")
if catalog.has_more: print(f"\nMore skills available (cursor: {catalog.next_cursor})")
```typescript TypeScript
// List skills in the catalog
const catalog = await client.skills.listCatalog({ limit: 20 });
console.log('Available skills:');
catalog.items.forEach(item => {
console.log(` - ${item.name}: ${item.description}`);
});
// Check if more skills are available
if (catalog.has_more) {
console.log(`\nMore skills available (cursor: ${catalog.next_cursor})`);
}
print(f"Skill: {skill.name}") print(f"Description: {skill.description}") print(f"\nFiles ({len(skill.file_index)}):") for file_info in skill.file_index: print(f" - {file_info.path} ({file_info.mime})")
```typescript TypeScript
// Get skill by ID
const skillDetails = await client.skills.get(skill.id);
console.log(`Skill: ${skillDetails.name}`);
console.log(`Description: ${skillDetails.description}`);
console.log(`\nFiles (${skillDetails.file_index.length}):`);
skillDetails.file_index.forEach(fileInfo => {
console.log(` - ${fileInfo.path} (${fileInfo.mime})`);
});
print(f"File: {result.path}") print(f"MIME: {result.mime}")
if result.content: print(f"\nContent ({result.content.type}):") print(result.content.raw)
if result.url: print(f"\nDownload URL: {result.url}")
```typescript TypeScript
// Read the SKILL.md file (always start here!)
const result = await client.skills.getFile({
skillId: skill.id,
filePath: 'SKILL.md'
});
console.log(`File: ${result.path}`);
console.log(`MIME: ${result.mime}`);
if (result.content) {
console.log(`\nContent (${result.content.type}):`);
console.log(result.content.raw);
}
if (result.url) {
console.log(`\nDownload URL: ${result.url}`);
}
The response includes:
path: File path within the skillmime: MIME type of the filecontent: Parsed content for text files (markdown, code, JSON, etc.)url: Presigned download URL for binary files (images, PDFs, etc.)
if script.content: print(f"Script content:\n{script.content.raw}")
template = client.skills.get_file( skill_id=skill.id, file_path="templates/output.json" )
if template.content: print(f"Template:\n{template.content.raw}")
```typescript TypeScript
// Read a Python script from the skill
const script = await client.skills.getFile({
skillId: skill.id,
filePath: 'scripts/extract.py'
});
if (script.content) {
console.log(`Script content:\n${script.content.raw}`);
}
// Read a JSON template
const template = await client.skills.getFile({
skillId: skill.id,
filePath: 'templates/output.json'
});
if (template.content) {
console.log(`Template:\n${template.content.raw}`);
}
// Delete the skill
await client.skills.delete(skill.id);
console.log('Skill deleted');Here's a complete working example that demonstrates the full workflow:
```python Python import os from acontext import AcontextClient, FileUploaddef main(): # Initialize client client = AcontextClient( api_key=os.getenv("ACONTEXT_API_KEY"), )
# If you're using self-hosted Acontext:
# client = AcontextClient(
# base_url="http://localhost:8029/api/v1",
# api_key="sk-ac-your-root-api-bearer-token",
# )
try:
# Upload a skill
with open("my-skill.zip", "rb") as f:
skill = client.skills.create(
file=FileUpload(filename="my-skill.zip", content=f.read()),
meta={"version": "1.0"}
)
print(f"✓ Created skill: {skill.name} ({skill.id})")
# List skill catalog
catalog = client.skills.list_catalog()
print(f"✓ Found {len(catalog.items)} skill(s) in catalog")
# Get skill details
skill_details = client.skills.get(skill.id)
print(f"✓ Skill has {len(skill_details.file_index)} file(s)")
# Read the SKILL.md
readme = client.skills.get_file(
skill_id=skill.id,
file_path="SKILL.md"
)
print(f"✓ Read SKILL.md ({len(readme.content.raw)} chars)")
# Clean up
client.skills.delete(skill.id)
print("✓ Deleted skill")
except Exception as e:
print(f"✗ Error: {e}")
if name == "main": main()
```typescript TypeScript
import { AcontextClient, FileUpload } from '@acontext/acontext';
import * as fs from 'fs';
async function main() {
// Initialize client
const client = new AcontextClient({
apiKey: process.env.ACONTEXT_API_KEY,
});
// If you're using self-hosted Acontext:
// const client = new AcontextClient({
// baseUrl: 'http://localhost:8029/api/v1',
// apiKey: 'sk-ac-your-root-api-bearer-token',
// });
try {
// Upload a skill
const zipContent = fs.readFileSync('my-skill.zip');
const skill = await client.skills.create({
file: new FileUpload({
filename: 'my-skill.zip',
content: zipContent,
contentType: 'application/zip'
}),
meta: { version: '1.0' }
});
console.log(`✓ Created skill: ${skill.name} (${skill.id})`);
// List skill catalog
const catalog = await client.skills.listCatalog();
console.log(`✓ Found ${catalog.items.length} skill(s) in catalog`);
// Get skill details
const skillDetails = await client.skills.get(skill.id);
console.log(`✓ Skill has ${skillDetails.file_index.length} file(s)`);
// Read the SKILL.md
const readme = await client.skills.getFile({
skillId: skill.id,
filePath: 'SKILL.md'
});
console.log(`✓ Read SKILL.md (${readme.content?.raw.length} chars)`);
// Clean up
await client.skills.delete(skill.id);
console.log('✓ Deleted skill');
} catch (error) {
console.error(`✗ Error: ${error}`);
}
}
main();
for skill in result.items: print(f"Skill: {skill.name}")
if result.has_more: next_result = client.skills.list_catalog( limit=10, cursor=result.next_cursor, time_desc=True )
```typescript TypeScript
// List skills with pagination
const result = await client.skills.listCatalog({
limit: 10,
timeDesc: true
});
result.items.forEach(skill => {
console.log(`Skill: ${skill.name}`);
});
// Get next page if available
if (result.has_more) {
const nextResult = await client.skills.listCatalog({
limit: 10,
cursor: result.next_cursor,
timeDesc: true
});
}
for skill in user_skills.items: print(f" - {skill.name}: {skill.description}")
```typescript TypeScript
// List skills for a specific user
const userSkills = await client.skills.listCatalog({
user: 'alice@example.com'
});
userSkills.items.forEach(skill => {
console.log(` - ${skill.name}: ${skill.description}`);
});
print(f"Download URL (valid for 1 hour): {result.url}")
```typescript TypeScript
// Get file with custom expiration (1 hour)
const result = await client.skills.getFile({
skillId: skill.id,
filePath: 'images/diagram.png',
expire: 3600 // 1 hour in seconds
});
console.log(`Download URL (valid for 1 hour): ${result.url}`);
Solution:
- Ensure the ZIP contains a
SKILL.mdfile at the root level - Check that
SKILL.mdhas valid YAML frontmatter withnameanddescription - Verify the ZIP is not corrupted
Solution:
- Use
client.skills.get(skill_id)to see thefile_indexand verify the file exists - Ensure the
file_pathmatches exactly (case-sensitive) - Check for leading slashes - paths should be relative (e.g.,
scripts/main.py, not/scripts/main.py)
Solution:
- Skill names must be unique within your project
- Either delete the existing skill first, or use a different name in your
SKILL.md