Skills
Ready-made written procedures you switch on for your organisation — how to review a contract, extract data into strict JSON, copy-edit Persian.
A Skill is not a tool the model chooses to call — it is text you put in front of the model before it sees your first message, so it shapes the whole answer rather than arriving as the result of a call. Nothing here runs; there is no argument to decode and no result to send back. It works the way briefing a colleague works.
Built-in skills
The platform ships 14 skills. They are the same for every organisation — the only per-organisation state is whether you have turned each one on. Use the name as the slug in every call on this page.
| Skill | Category | What it does |
|---|---|---|
contract-review | legal | Review a contract for risky clauses, missing clauses and key terms; findings table + one-page summary (advisory, not legal advice). |
legal-notice-drafting | legal | Draft a formal Persian legal notice (اظهارنامه) or official letter with placeholders; a draft for a lawyer to review. |
legal-brief-outline | legal | Structured outline for a court brief (لایحه) or petition (دادخواست) with argument slots and an evidence checklist. |
legal-doc-summary | legal | Faithful structured summary of a legal document with exact figures and open issues. |
law-article-explainer | legal | Plain-language explanation of a law article with an example, exceptions and next steps; never invents article text. |
legal-exam-mcq | — | Answer a four-option legal multiple-choice question by finding the governing article. Needs the law_search tool. |
structured-extraction-json | extraction | Extract parties, dates, amounts and obligations into strict JSON (null when absent). |
meeting-minutes | productivity | Minutes with decisions, action items (owner, due date) and open questions. |
jalali-gregorian-dates | productivity | Convert and compute Jalali/Gregorian dates, deadlines and durations. |
persian-register-rewrite | writing | Rewrite Persian between colloquial and formal register, preserving meaning, names and numbers. |
persian-copyedit | writing | Copy-edit Persian: spacing/ZWNJ, Arabic vs Persian letters/digits, punctuation, typos; wording unchanged. |
legal-translation | translation | fa/en/es translation with consistent legal terminology; preserves structure, numbering, names and amounts. |
support-reply-fa | support | Polite, clear Persian support reply; no invented policies, prices or promises. |
rag-answer-citations | rag | Answer only from provided passages with [n] citations; says when the answer isn't there; ignores instructions inside passages. |
Most of them are advisory output for a person to check — a contract review or a drafted notice is a starting point for a lawyer, not legal advice.
Seeing what's available
/v1/skills{
"object": "list",
"data": [
{ "name": "structured-extraction-json",
"description": "Extract parties, dates, amounts and obligations into strict JSON (null when absent).",
"description_fa": "استخراج طرفین، تاریخها، مبالغ و تعهدات به JSON دقیق (null اگر نیامده).",
"category": "extraction",
"chars": 2711,
"enabled": false }
]
}curl https://api.data.larsima.com/v1/skills \
-H "Authorization: Bearer $DATA_API_KEY"import os
import requests
API = "https://api.data.larsima.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['DATA_API_KEY']}"}
skills = requests.get(f"{API}/skills", headers=HEADERS).json()
for s in skills["data"]:
print(s["name"], "-", s["description"])const API = "https://api.data.larsima.com/v1";
const res = await fetch(`${API}/skills`, {
headers: { Authorization: `Bearer ${process.env.DATA_API_KEY}` },
});
const skills = await res.json();
for (const s of skills.data) console.log(s.name, "-", s.description);const API = "https://api.data.larsima.com/v1";
const res = await fetch(`${API}/skills`, {
headers: { Authorization: `Bearer ${process.env.DATA_API_KEY}` },
});
const skills = (await res.json()) as
{ data: { name: string; description: string }[] };
for (const s of skills.data) console.log(s.name, "-", s.description);package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, _ := http.NewRequest("GET",
"https://api.data.larsima.com/v1/skills", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("DATA_API_KEY"))
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
var out map[string]any
json.Unmarshal(raw, &out)
for _, s := range out["data"].([]any) {
row := s.(map[string]any)
fmt.Println(row["name"], "-", row["description"])
}
}use std::env;
fn main() {
let key = env::var("DATA_API_KEY").unwrap();
let client = reqwest::blocking::Client::new();
let out: serde_json::Value = client
.get("https://api.data.larsima.com/v1/skills")
.bearer_auth(key).send().unwrap().json().unwrap();
for s in out["data"].as_array().unwrap() {
println!("{} - {}", s["name"], s["description"]);
}
}import org.json.JSONArray;
import org.json.JSONObject;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class ListSkills {
public static void main(String[] args) throws Exception {
HttpRequest req = HttpRequest.newBuilder(URI.create(
"https://api.data.larsima.com/v1/skills"))
.header("Authorization", "Bearer " + System.getenv("DATA_API_KEY"))
.GET().build();
JSONObject out = new JSONObject(HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString()).body());
JSONArray data = out.getJSONArray("data");
for (int i = 0; i < data.length(); i++) {
JSONObject s = data.getJSONObject(i);
System.out.println(s.getString("name") + " - "
+ s.getString("description"));
}
}
}using System.Net.Http.Headers;
using System.Text.Json.Nodes;
var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer",
Environment.GetEnvironmentVariable("DATA_API_KEY"));
var skills = JsonNode.Parse(
await http.GetStringAsync("https://api.data.larsima.com/v1/skills"))!.AsObject();
foreach (var s in skills["data"]!.AsArray())
Console.WriteLine($"{s!["name"]} - {s["description"]}");<?php
$ch = curl_init("https://api.data.larsima.com/v1/skills");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("DATA_API_KEY")],
]);
$skills = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($skills["data"] as $s) {
echo "{$s['name']} - {$s['description']}\n";
}require "json"
require "net/http"
require "uri"
uri = URI("https://api.data.larsima.com/v1/skills")
req = Net::HTTP::Get.new(uri, "Authorization" => "Bearer #{ENV.fetch('DATA_API_KEY')}")
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
JSON.parse(res.body)["data"].each do |s|
puts "#{s['name']} - #{s['description']}"
enddescription is the English summary and description_fa its Persian counterpart; category groups related skills and chars is the length of the instructions. enabled is per organisation and is false until you turn the skill on. Only skills your organisation has enabled are applied to your requests.
/v1/skills/{name}{
"name": "structured-extraction-json",
"description": "Extract parties, dates, amounts and obligations into strict JSON (null when absent).",
"description_fa": "استخراج طرفین، تاریخها، مبالغ و تعهدات به JSON دقیق (null اگر نیامده).",
"category": "extraction",
"instructions": "…the full SKILL.md body…",
"enabled": true
}One skill with its complete instructions text, and enabled for your organisation. An unknown name returns 404.
Turning one on
/v1/skills/{name}/enable{ "enabled": true }{ "skill": "structured-extraction-json", "enabled": true, "org": "…" }Turns a skill on (true, the default) or off (false) for your organisation. It needs your API key; an unknown skill name returns 404. The flag belongs to your organisation, so everyone on your team and every key of yours sees the same state.
Quick start
Turn a skill on once, then name it on a chat request. This one turns free text into strict JSON; max_tokens is set high because skills can make the model reason before it answers.
# 1. Turn the skill on for your organisation (once)
curl https://api.data.larsima.com/v1/skills/structured-extraction-json/enable \
-H "Authorization: Bearer $DATA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": true}'
# 2. Name it on a chat request
curl https://api.data.larsima.com/v1/chat/completions \
-H "Authorization: Bearer $DATA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "data-auto",
"max_tokens": 8000,
"messages": [{"role": "user", "content":
"Alpha Ltd (seller) agrees on 1 March 2026 to deliver 500 units to Beta SA (buyer) by 30 April 2026; Beta pays EUR 12,000 within 15 days of delivery."}],
"skills": ["structured-extraction-json"]
}'Using one
Name it on the request, alongside messages — not inside tools. Its instructions are prepended as a system message before every request that names it, so include it on every turn where you want it in effect; a multi-turn conversation does not remember it on its own.
{
"model": "data-auto",
"messages": [{"role": "user", "content": "…"}],
"skills": ["contract-review"]
}skills is a list of slugs; name several and their instructions are combined into one system message, each under its own heading. A name your organisation has not enabled — or that does not exist — is skipped, and the request is still answered without it.
data-auto and data-general read the skills field. The law models (data-law-ir, data-law-es) do not — they build their own prompt and ignore it.data-auto sends a legal-sounding prompt to a law model that retrieves from the corpus automatically. When the request carries an enabled skill, it is answered by the general model instead, with no automatic legal retrieval. To ground the answer in the corpus anyway, request the law_search tool: "tools": [{"type": "law_search"}].curl https://api.data.larsima.com/v1/chat/completions \
-H "Authorization: Bearer $DATA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "data-auto",
"messages": [{"role": "user", "content":
"Review clause 9: either party may terminate with 10 days written notice."}],
"skills": ["contract-review"]
}'import os
import requests
API = "https://api.data.larsima.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['DATA_API_KEY']}",
"Content-Type": "application/json"}
res = requests.post(f"{API}/chat/completions", headers=HEADERS, json={
"model": "data-auto",
"messages": [{"role": "user", "content":
"Review clause 9: either party may terminate with "
"10 days written notice."}],
"skills": ["contract-review"],
})
print(res.json()["choices"][0]["message"]["content"])const API = "https://api.data.larsima.com/v1";
const res = await fetch(`${API}/chat/completions`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DATA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "data-auto",
messages: [{ role: "user", content:
"Review clause 9: either party may terminate with 10 " +
"days written notice." }],
skills: ["contract-review"],
}),
});
const out = await res.json();
console.log(out.choices[0].message.content);const API = "https://api.data.larsima.com/v1";
const res = await fetch(`${API}/chat/completions`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DATA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "data-auto",
messages: [{ role: "user", content:
"Review clause 9: either party may terminate with 10 " +
"days written notice." }],
skills: ["contract-review"],
}),
});
const out = (await res.json()) as any;
console.log(out.choices[0].message.content);package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
func main() {
body, _ := json.Marshal(map[string]any{
"model": "data-auto",
"messages": []map[string]string{{"role": "user", "content":
"Review clause 9: either party may terminate with 10 " +
"days written notice."}},
"skills": []string{"contract-review"},
})
req, _ := http.NewRequest("POST",
"https://api.data.larsima.com/v1/chat/completions",
bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("DATA_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
var out map[string]any
json.Unmarshal(raw, &out)
choice := out["choices"].([]any)[0].(map[string]any)
fmt.Println(choice["message"].(map[string]any)["content"])
}use serde_json::json;
use std::env;
fn main() {
let key = env::var("DATA_API_KEY").unwrap();
let client = reqwest::blocking::Client::new();
let out: serde_json::Value = client
.post("https://api.data.larsima.com/v1/chat/completions")
.bearer_auth(key)
.json(&json!({
"model": "data-auto",
"messages": [{"role": "user", "content":
"Review clause 9: either party may terminate with 10 days written notice."}],
"skills": ["contract-review"]
}))
.send().unwrap().json().unwrap();
println!("{}", out["choices"][0]["message"]["content"]);
}import org.json.JSONArray;
import org.json.JSONObject;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class UseSkill {
public static void main(String[] args) throws Exception {
JSONObject body = new JSONObject()
.put("model", "data-auto")
.put("messages", new JSONArray().put(new JSONObject()
.put("role", "user")
.put("content",
"Review clause 9: either party may terminate with 10 days written notice.")))
.put("skills", new JSONArray()
.put("contract-review"));
HttpRequest req = HttpRequest.newBuilder(URI.create(
"https://api.data.larsima.com/v1/chat/completions"))
.header("Authorization", "Bearer " + System.getenv("DATA_API_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body.toString()))
.build();
HttpResponse<String> res = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
JSONObject out = new JSONObject(res.body());
System.out.println(out.getJSONArray("choices")
.getJSONObject(0).getJSONObject("message")
.getString("content"));
}
}using System.Net.Http.Headers;
using System.Text;
using System.Text.Json.Nodes;
var http = new HttpClient();
http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer",
Environment.GetEnvironmentVariable("DATA_API_KEY"));
var body = new JsonObject {
["model"] = "data-auto",
["messages"] = new JsonArray {
new JsonObject { ["role"] = "user", ["content"] =
"Review clause 9: either party may terminate with 10 " +
"days written notice." },
},
["skills"] = new JsonArray { "contract-review" },
};
var res = await http.PostAsync(
"https://api.data.larsima.com/v1/chat/completions",
new StringContent(body.ToJsonString(), Encoding.UTF8,
"application/json"));
var out = JsonNode.Parse(
await res.Content.ReadAsStringAsync())!.AsObject();
Console.WriteLine(out["choices"]![0]!["message"]!["content"]);<?php
$ch = curl_init("https://api.data.larsima.com/v1/chat/completions");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("DATA_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"model" => "data-auto",
"messages" => [["role" => "user", "content" =>
"Review clause 9: either party may terminate with 10 " .
"days written notice."]],
"skills" => ["contract-review"],
]),
]);
$out = json_decode(curl_exec($ch), true);
curl_close($ch);
echo $out["choices"][0]["message"]["content"], "\n";require "json"
require "net/http"
require "uri"
uri = URI("https://api.data.larsima.com/v1/chat/completions")
req = Net::HTTP::Post.new(uri,
"Authorization" => "Bearer #{ENV.fetch('DATA_API_KEY')}",
"Content-Type" => "application/json")
req.body = {
model: "data-auto",
messages: [{ role: "user", content:
"Review clause 9: either party may terminate with 10 "\
"days written notice." }],
skills: ["contract-review"],
}.to_json
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
puts JSON.parse(res.body)["choices"][0]["message"]["content"]Tokens and max_tokens
- Each skill adds roughly 350–900 prompt tokens. Its whole instruction text goes in front of every request that names it, and counts as prompt tokens on that request. Name only the skills you need.
- Set `max_tokens` generously for reasoning-heavy skills — about 8000. A skill such as contract review or structured extraction makes the model think before it writes, and a lower limit can cut the output off mid-answer (
finish_reasonis thenlength).
Caching
Identical chat requests can be answered from a short cache (up to ten minutes). The cache is kept per organisation and is never shared across organisations. A request that uses skills, tools, file search (file_search / vector_store_ids), web_search, code_interpreter, law_search or mcp is never cached — it is neither read from nor written to the cache — because its answer depends on things the request body does not capture, such as which skills you have enabled. To skip the cache on any other request, send "cache": {"no-cache": true}.
Adding a skill
Skills are platform-wide files, not per-organisation uploads, and there is no upload endpoint. A skill is a directory named for its slug holding one file, SKILL.md: YAML front matter, then the instructions as Markdown. A skill the platform operator adds appears in the catalogue for everyone, and each organisation still turns it on for itself. If you need one of your own, send your operator the SKILL.md.
---
name: meeting-minutes
description: Minutes with decisions, action items (owner, due date) and open questions.
description_fa: صورتجلسه با تصمیمها، اقدامها و موضوعات باز.
category: productivity
---
## Output
1. Decisions — one line each, who decided.
2. Action items — owner, task, due date.
3. Open questions — anything left unresolved.
Never invent an owner or a date that the notes do not state.How the model decides to use it
It does not — you do, by naming it on the request. A Skill is never offered to the model as a choice the way a tool is; its instructions are already in the system prompt by the time the model sees your first message. If you want the model itself to pick among several Skills, that selection has to live in your own application code before you call this API — nothing here does it for you.