Tacsmith API
A JSON-over-HTTPS API for asking an agent for its next decision, configuring its behaviour, running simulations and reading analytics. Every agent uses the same pattern: send the state, receive the action.
| Base URL | https://api.tacsmith.com/v1 |
|---|---|
| Format | JSON request and response bodies, UTF-8 |
| Authentication | Bearer API key in the Authorization header |
| Versioning | The version is in the path. Breaking changes ship as a new version, and v1 stays supported for at least 12 months after v2. |
Authentication
Create an API key from your account dashboard. Send it with every request:
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxx
Keys starting sk_test_ run against a sandbox with the same responses but no billing. Keep keys on your server.
Never put them in a game client or a public repository. If a key leaks, rotate it from the dashboard.
Quickstart
Ask the chess agent for a move with a single request. Set TACSMITH_API_KEY in your environment first.
curl
curl https://api.tacsmith.com/v1/chess/move \
-H "Authorization: Bearer $TACSMITH_API_KEY" \
-H "Content-Type: application/json" \
-d '{"fen": "startpos", "limits": {"time_ms": 100}}'
Python
import os, requests
API = "https://api.tacsmith.com/v1"
headers = {"Authorization": f"Bearer {os.environ['TACSMITH_API_KEY']}"}
r = requests.post(f"{API}/chess/move", headers=headers, json={
"fen": "startpos",
"bot": "ai-latest",
"profile": "club-player",
"limits": {"time_ms": 200},
})
r.raise_for_status()
print(r.json()["move"]) # e.g. "e2e4"
JavaScript
const res = await fetch("https://api.tacsmith.com/v1/chess/move", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.TACSMITH_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ fen: "startpos", limits: { time_ms: 200 } }),
});
const { move, eval: score } = await res.json();
Errors and limits
Errors use standard HTTP status codes and a consistent body:
{ "error": { "code": "invalid_position", "message": "FEN has two white kings", "request_id": "req_8f2c..." } }
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request, invalid_position | The body is malformed or the game state is illegal. |
| 401 | unauthorised | The key is missing or invalid. |
| 403 | plan_required | The feature isn't included in your plan. |
| 404 | not_found | The agent, profile, session or simulation doesn't exist. |
| 429 | rate_limited | Too many requests. Retry after the number of seconds in the Retry-After header. |
| 5xx | server_error | Something failed on our side. It's safe to retry with backoff. |
Every response includes X-RateLimit-Remaining and X-Request-Id headers.
Include the request ID when contacting support.
GET/agents
Lists the agents your key can use, with their status and capabilities.
{
"data": [
{ "id": "chess", "status": "available", "bots": ["ai-latest", "ai-strong", "ai-beginner"],
"capabilities": ["move", "analyse", "profiles", "sessions"] },
{ "id": "racing", "status": "early_access",
"capabilities": ["sessions"] }
]
}
POST/chess/move
Returns the agent's chosen move for a position.
| Field | Type | Description |
|---|---|---|
fen | string | Required. The position in FEN, or "startpos". |
moves | string[] | Optional moves in UCI format to apply after fen. Use this to send the game history so repetitions are detected. |
bot | string | Which trained bot to use. Default ai-latest. |
profile | string | The ID of a behaviour profile (see profiles). |
limits.time_ms | integer | Thinking time, 10 to 10,000 ms. Default 200. |
limits.nodes | integer | Use a node budget instead of time, for repeatable results. |
Response
{
"move": "g1f3",
"eval": 0.18, // from the side to move's view, -1 to +1
"mate_in": null, // a number when a forced mate is found
"depth": 15,
"nodes": 64210,
"time_ms": 198,
"pv": ["g1f3", "b8c6", "f1b5"]
}
POST/chess/analyse
Returns the top candidate moves with evaluations. This is useful for hints, post-game review and coaching features.
It takes the same fields as /chess/move, plus multipv (1 to 5).
{
"lines": [
{ "move": "e2e4", "eval": 0.21, "pv": ["e2e4", "e7e5", "g1f3"] },
{ "move": "d2d4", "eval": 0.19, "pv": ["d2d4", "d7d5", "c2c4"] }
]
}
Behaviour profiles
Profiles change how an agent plays without retraining it. Use them to set difficulty, style and personality.
GET/profiles lists the preset and custom profiles.
POST/profiles creates a custom profile (Indie plan and above).
PUT/profiles/{id} updates one.
DELETE/profiles/{id} deletes one.
POST /v1/profiles
{
"name": "aggressive-club",
"agent": "chess",
"strength": 0.55, // 0 = beginner, 1 = full strength
"aggression": 0.8, // prefers attacking, imbalanced lines
"risk": 0.6, // willingness to play objectively dubious moves
"contempt": 0.2, // avoids draws when ahead
"mistake_rate": 0.05 // occasional human-like inaccuracies
}
Presets include beginner, casual, club-player, expert and full-strength.
Sessions Early access
A session keeps a server-side agent running with its own state. You stream observations to it and it returns actions, which suits real-time games and control agents such as the racing agent.
POST/sessions starts a session.
POST/sessions/{id}/step sends an observation and receives an action.
GET/sessions/{id} returns the session's status and statistics.
DELETE/sessions/{id} ends it.
POST /v1/sessions
{ "agent": "racing", "profile": "safe-lines", "decision_ms": 30 }
→ { "id": "ses_41b9...", "agent": "racing", "status": "ready" }
POST /v1/sessions/ses_41b9.../step
{
"t_ms": 12450,
"observation": {
"speed_kmh": 312.4,
"tilt": [0.01, -0.02],
"wheels_on_ground": [true, true, true, true],
"wall_distances": [14.2, 9.8, 22.1, 6.5],
"track_ahead": [[0, 0], [12.1, 0.4], [24.0, 1.9]]
}
}
→ { "action": { "steer": -0.35, "throttle": 1.0, "brake": false }, "latency_ms": 4 }
Simulations Early access
Simulations run many agent-vs-agent games or episodes in the background. Use them to measure difficulty curves, tune profiles or test balance before release. Studio plan and above.
POST/simulations starts a run.
GET/simulations/{id} returns progress and results.
POST /v1/simulations
{
"agent": "chess",
"players": [
{ "bot": "ai-latest", "profile": "club-player" },
{ "bot": "ai-latest", "profile": "aggressive-club" }
],
"games": 200,
"limits": { "nodes": 20000 },
"openings": "balanced-50"
}
GET /v1/simulations/sim_9a7e...
→ { "status": "complete", "games": 200,
"results": { "wins": [71, 64], "draws": 65 },
"elo_diff": 12, "elo_error": 38 }
GET/analytics/summary Early access
Returns aggregated statistics for your agents over a date range: request volume, decision latency, results and how players fared against each profile.
GET /v1/analytics/summary?agent=chess&from=2026-10-01&to=2026-10-31&group_by=profile
{
"period": { "from": "2026-10-01", "to": "2026-10-31" },
"groups": [
{ "profile": "casual", "games": 4210, "player_win_rate": 0.46, "avg_game_moves": 38, "p95_latency_ms": 212 },
{ "profile": "expert", "games": 1180, "player_win_rate": 0.09, "avg_game_moves": 44, "p95_latency_ms": 405 }
]
}
Reading analytics doesn't count towards your API call allowance. Studio and Enterprise plans can export raw events as CSV or JSON.
Integration guide
1. Call the API from your server
Put a thin service between your game and the API so the key never reaches players. The game sends state to your service, and your service calls Tacsmith and returns the action.
2. Match the call pattern to the game
- Turn-based (chess, strategy, card games): one stateless call per turn to
/chess/moveor the equivalent agent endpoint. - Real-time (racing, action, movement): open a session and call
/stepat the agent's decision interval. Keep the connection alive and reuse it. - Offline (balancing, QA): use simulations and poll for results.
3. Tune behaviour with profiles
Start from a preset, then create custom profiles per difficulty level or character. Use analytics to check player win rates and adjust.
4. Handle failure gracefully
Set a client timeout a little above your thinking limit. On a timeout or 5xx, retry once with backoff, then fall back to a
simple local move so the game never stalls.
def agent_move(state, retries=1):
for attempt in range(retries + 1):
try:
r = session.post(f"{API}/chess/move", json=state, timeout=1.5)
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", 1)))
continue
r.raise_for_status()
return r.json()["move"]
except requests.RequestException:
time.sleep(0.2 * (attempt + 1))
return local_fallback_move(state)
Licensing
Tacsmith's agents are closed source. The models, engines and training code are proprietary and aren't published or licensed for redistribution. You use them only through the hosted API.
The Personal plan is for learning and non-commercial projects. Each paid plan includes a commercial licence to use agent decisions in the titles it covers.