API reference · v1

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 URLhttps://api.tacsmith.com/v1
FormatJSON request and response bodies, UTF-8
AuthenticationBearer API key in the Authorization header
VersioningThe version is in the path. Breaking changes ship as a new version, and v1 stays supported for at least 12 months after v2.
Endpoints marked Early access belong to systems that are still being built (see In Development). Their shape may change before general release.

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..." } }
StatusCodeMeaning
400invalid_request, invalid_positionThe body is malformed or the game state is illegal.
401unauthorisedThe key is missing or invalid.
403plan_requiredThe feature isn't included in your plan.
404not_foundThe agent, profile, session or simulation doesn't exist.
429rate_limitedToo many requests. Retry after the number of seconds in the Retry-After header.
5xxserver_errorSomething 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.

FieldTypeDescription
fenstringRequired. The position in FEN, or "startpos".
movesstring[]Optional moves in UCI format to apply after fen. Use this to send the game history so repetitions are detected.
botstringWhich trained bot to use. Default ai-latest.
profilestringThe ID of a behaviour profile (see profiles).
limits.time_msintegerThinking time, 10 to 10,000 ms. Default 200.
limits.nodesintegerUse 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/move or the equivalent agent endpoint.
  • Real-time (racing, action, movement): open a session and call /step at 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.