Skills
Add Skill
POST
/
api
/
v1
/
skills
Add Skill
curl --request POST \
--url https://api.example.com/api/v1/skills \
--header 'Content-Type: application/json' \
--data '
{
"data": {},
"wait": true,
"timeout": 123
}
'import requests
url = "https://api.example.com/api/v1/skills"
payload = {
"data": {},
"wait": True,
"timeout": 123
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({data: {}, wait: true, timeout: 123})
};
fetch('https://api.example.com/api/v1/skills', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/v1/skills",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'data' => [
],
'wait' => true,
'timeout' => 123
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/v1/skills"
payload := strings.NewReader("{\n \"data\": {},\n \"wait\": true,\n \"timeout\": 123\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/api/v1/skills")
.header("Content-Type", "application/json")
.body("{\n \"data\": {},\n \"wait\": true,\n \"timeout\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/skills")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"data\": {},\n \"wait\": true,\n \"timeout\": 123\n}"
response = http.request(request)
puts response.read_body{
"status": "ok",
"result": {
"status": "success",
"uri": "viking://agent/skills/search-web/",
"name": "search-web",
"auxiliary_files": 0
},
"time": 0.15
}
{
"status": "ok",
"result": {
"status": "success",
"uri": "viking://agent/skills/code-runner/",
"name": "code-runner",
"auxiliary_files": 3
},
"time": 0.25
}
Add a skill to the knowledge base. Skills define capabilities that agents can invoke. OpenViking automatically detects and converts MCP tool definitions to skill format.
Or for directories with auxiliary files:
Output (Skill):
Skills are vectorized and indexed for semantic retrieval. Use
wait=true or call /api/v1/system/wait to ensure processing completes.Authentication
Requires API key authentication viaX-API-Key header.
Request Body
object | string
required
Skill data in one of four supported formats:
- Skill format (dict with name, description, content)
- MCP Tool format (dict with inputSchema - auto-converted)
- String (SKILL.md content with YAML frontmatter)
- Path (file or directory path)
boolean
default:"false"
Wait for vectorization to complete before returning
number
default:"null"
Timeout in seconds when
wait=trueSkill Data Formats
Format 1: Skill Dict
{
"name": "search-web",
"description": "Search the web for current information",
"content": "# search-web\n\nSearch the web for current information.\n\n## Parameters\n- **query** (string, required): Search query\n- **limit** (integer, optional): Max results, default 10",
"allowed_tools": ["HttpClient", "JsonParser"],
"tags": ["web", "search"]
}
Format 2: MCP Tool (Auto-Converted)
{
"name": "calculator",
"description": "Perform mathematical calculations",
"inputSchema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Mathematical expression to evaluate"
}
},
"required": ["expression"]
}
}
OpenViking detects MCP format by the presence of
inputSchema and automatically converts it to skill format with generated markdown documentation.Format 3: SKILL.md String
---
name: code-runner
description: Execute code in various languages
allowed-tools:
- FileSystem
- ProcessRunner
tags:
- code
- execution
---
# code-runner
Execute code in various languages safely.
## Parameters
- **language** (string, required): Programming language
- **code** (string, required): Code to execute
- **timeout** (integer, optional): Timeout in seconds
Format 4: File Path
{
"data": "./skills/search-web/SKILL.md"
}
{
"data": "./skills/code-runner/"
}
Response
string
Response status (
ok or error)object
number
Request processing time in seconds
Examples
curl -X POST http://localhost:1933/api/v1/skills \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"data": {
"name": "search-web",
"description": "Search the web for current information",
"content": "# search-web\n\nSearch the web for current information.\n\n## Parameters\n- **query** (string, required): Search query\n- **limit** (integer, optional): Max results, default 10"
}
}'
curl -X POST http://localhost:1933/api/v1/skills \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"data": {
"name": "calculator",
"description": "Perform mathematical calculations",
"inputSchema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Mathematical expression to evaluate"
}
},
"required": ["expression"]
}
}
}'
# Add from skill dict
skill = {
"name": "search-web",
"description": "Search the web for current information",
"content": """
# search-web
Search the web for current information.
## Parameters
- **query** (string, required): Search query
- **limit** (integer, optional): Max results, default 10
"""
}
result = client.add_skill(skill)
print(f"Added: {result['uri']}")
# Add from MCP tool (auto-converted)
mcp_tool = {
"name": "calculator",
"description": "Perform mathematical calculations",
"inputSchema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Mathematical expression to evaluate"
}
},
"required": ["expression"]
}
}
result = client.add_skill(mcp_tool)
print(f"Added: {result['uri']}")
# Add from file
result = client.add_skill("./skills/search-web/SKILL.md")
print(f"Added: {result['uri']}")
# Add from directory (includes auxiliary files)
result = client.add_skill("./skills/code-runner/")
print(f"Added: {result['uri']}")
print(f"Auxiliary files: {result['auxiliary_files']}")
# Add from file
openviking add-skill ./skills/search-web/SKILL.md
# Add from directory and wait for processing
openviking add-skill ./skills/code-runner/ --wait
{
"status": "ok",
"result": {
"status": "success",
"uri": "viking://agent/skills/search-web/",
"name": "search-web",
"auxiliary_files": 0
},
"time": 0.15
}
{
"status": "ok",
"result": {
"status": "success",
"uri": "viking://agent/skills/code-runner/",
"name": "code-runner",
"auxiliary_files": 3
},
"time": 0.25
}
MCP Tool Conversion
When you provide a dict withinputSchema, OpenViking automatically converts it to skill format:
Input (MCP):
{
"name": "search_web",
"description": "Search the web",
"inputSchema": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"},
"limit": {"type": "integer", "description": "Max results"}
},
"required": ["query"]
}
}
---
name: search-web
description: Search the web
---
# search-web
Search the web
## Parameters
- **query** (string) (required): Search query
- **limit** (integer) (optional): Max results
## Usage
This tool wraps the MCP tool `search-web`. Call this when the user needs functionality matching the description above.
Best Practices
Use Clear Descriptions
Use Clear Descriptions
# Good - specific and actionable
skill = {
"name": "search-web",
"description": "Search the web for current information using Google",
...
}
# Less helpful - too vague
skill = {
"name": "search",
"description": "Search",
...
}
Use Kebab-Case for Names
Use Kebab-Case for Names
search-web✅searchWeb❌search_web❌
Include Comprehensive Content
Include Comprehensive Content
- Clear parameter descriptions with types
- When to use the skill
- Concrete examples
- Edge cases and limitations
Related Endpoints
- List Skills - View all skills
- Search Skills - Semantic search
- Remove Skill - Delete a skill
- Wait for Processing - Ensure vectorization completes
Add Skill
curl --request POST \
--url https://api.example.com/api/v1/skills \
--header 'Content-Type: application/json' \
--data '
{
"data": {},
"wait": true,
"timeout": 123
}
'import requests
url = "https://api.example.com/api/v1/skills"
payload = {
"data": {},
"wait": True,
"timeout": 123
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({data: {}, wait: true, timeout: 123})
};
fetch('https://api.example.com/api/v1/skills', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.example.com/api/v1/skills",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'data' => [
],
'wait' => true,
'timeout' => 123
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.example.com/api/v1/skills"
payload := strings.NewReader("{\n \"data\": {},\n \"wait\": true,\n \"timeout\": 123\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.example.com/api/v1/skills")
.header("Content-Type", "application/json")
.body("{\n \"data\": {},\n \"wait\": true,\n \"timeout\": 123\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.example.com/api/v1/skills")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"data\": {},\n \"wait\": true,\n \"timeout\": 123\n}"
response = http.request(request)
puts response.read_body{
"status": "ok",
"result": {
"status": "success",
"uri": "viking://agent/skills/search-web/",
"name": "search-web",
"auxiliary_files": 0
},
"time": 0.15
}
{
"status": "ok",
"result": {
"status": "success",
"uri": "viking://agent/skills/code-runner/",
"name": "code-runner",
"auxiliary_files": 3
},
"time": 0.25
}
