{"schema_version":"2026-10-01","name":"The Model Company","description":"Create small, specialized AI models from a task description and examples, then export them.","status":"preview","interfaces":{"web":{"status":"live","url":"/","note":"Demo jobs are simulated; no real training in the preview."},"skill":{"status":"live","url":"/SKILL.md","setup":"Paste 'Set up <origin>/SKILL.md' into an agent.","note":"An Agent Skill that helps a person build a model in the web app. It doesn't call the planned API."},"rest":{"status":"planned","base_url":"https://api.tmc.example","openapi":"/openapi.json"},"mcp":{"status":"planned","url":"https://mcp.tmc.example/v1","transport":"streamable-http"},"cli":{"status":"planned","command":"tmc"}},"documentation":{"human":"/agents","reference":"/agents/reference","skill":"/SKILL.md","llms_txt":"/llms.txt","openapi":"/openapi.json"},"auth":{"type":"bearer","scopes":{"models:read":"Read models, jobs, estimates and evaluations","datasets:write":"Register training data","jobs:create":"Start training jobs (spends money, within caps)","jobs:cancel":"Cancel running jobs","models:predict":"Run predictions (hosted models bill per call)","models:export":"Download weights and containers"},"note":"Keys are scoped and carry their own spending limits. Agents should request the narrowest scopes they need."},"policies":{"idempotency":{"header":"Idempotency-Key","required_for":["create_model_job"],"retention_hours":24,"conflict_status":409},"spending":{"per_job_cap":"requirements.max_spend_usd (required)","per_key_monthly_limit":true,"auto_approve_limit_usd":25,"above_limit":"402 approval_required with an approval_url for a human","dry_run":"estimate_cost is free"},"rate_limits":{"requests_per_minute":120,"header":"Retry-After"}},"long_running_jobs":{"create":"POST /v1/jobs → 202 Accepted + Location + Retry-After","poll":"GET /v1/jobs/{job_id}","states":["queued","running","succeeded","failed","canceled"],"stages":["validate","prepare","select","train","evaluate","package"],"webhooks":["job.succeeded","job.failed","job.canceled"]},"artifacts":{"formats":["onnx","safetensors","gguf","docker","hosted_endpoint"],"integrity":"sha256","signed_url_ttl_seconds":900,"includes_model_card":true},"tools":[{"name":"describe_capabilities","description":"What this service can build, the supported output kinds, export formats, limits and current status of each interface.","effect":"read","scopes":[],"rest":"GET /v1/capabilities","cli":"tmc capabilities","input_schema":{"type":"object","properties":{}}},{"name":"estimate_cost","description":"Free dry run. Returns a cost range, wall-clock time and whether it fits under the cap. Call this before create_model_job.","effect":"read","scopes":["models:read"],"rest":"POST /v1/estimates","cli":"tmc estimate --task … --rows 2400 --min-accuracy 0.9","input_schema":{"type":"object","properties":{"output_kind":{"type":"string","enum":["single_label","multi_label","extraction"],"description":"single_label: pick exactly one category. multi_label: pick any number of tags. extraction: return named fields."},"label_count":{"type":"integer","minimum":0,"maximum":50},"field_count":{"type":"integer","minimum":0,"maximum":30},"row_count":{"type":"integer","minimum":0,"maximum":9007199254740991},"requirements":{"type":"object","properties":{"min_accuracy":{"type":"number","minimum":0.5,"maximum":0.995,"description":"Quality bar on the held-out test set (0–1)."},"max_latency_ms":{"type":"integer","minimum":1,"maximum":10000,"description":"p95 latency budget per prediction, in ms."},"deployment":{"type":"string","enum":["hosted_api","self_hosted","on_device"],"description":"Where the finished model will run. Affects candidate selection and packaging."},"max_spend_usd":{"type":"number","minimum":0,"maximum":10000,"description":"Hard spending cap for this job. The job stops before exceeding it."},"third_party":{"default":null,"description":"Null (the default): only models that run on our servers can be chosen. Set: models that call a third-party model can be tried, chosen and deployed too. Separate from `compare_with_llms`, which only scores general-purpose LLMs for comparison.","anyOf":[{"type":"object","properties":{"daily_cap_usd":{"type":"number","minimum":0.01,"maximum":10000,"description":"Live calls to such a model stop for the day once they've cost this much, and callers fall back to the model they use today."}},"required":["daily_cap_usd"],"description":"Allows models that call a third-party model, such as Claude through Anthropic's API, for every answer. If one is chosen and deployed, every live call goes to that provider, with emails and long numbers masked."},{"type":"null"}]}},"required":["min_accuracy","max_latency_ms","deployment","max_spend_usd"]},"advanced":{"type":"object","properties":{"base_models":{"anyOf":[{"type":"string","const":"auto"},{"minItems":1,"type":"array","items":{"type":"string"}}],"description":"`auto` lets the planner choose candidates; or pin specific base models."},"max_candidates":{"type":"integer","minimum":1,"maximum":8},"validation_split":{"type":"number","minimum":0.05,"maximum":0.4},"epochs":{"anyOf":[{"type":"string","const":"auto"},{"type":"integer","minimum":1,"maximum":50}]},"learning_rate":{"anyOf":[{"type":"string","const":"auto"},{"type":"number","exclusiveMinimum":0,"maximum":1}]},"quantization":{"type":"string","enum":["none","int8","int4"]},"early_stopping":{"type":"boolean"},"seed":{"type":"integer","minimum":0,"maximum":2147483647}},"required":["base_models","max_candidates","validation_split","epochs","learning_rate","quantization","early_stopping","seed"]},"compare_with_llms":{"description":"Whether the job will also score general-purpose LLMs (see CreateJobRequest).","type":"boolean"}},"required":["output_kind","label_count","field_count","row_count","requirements"]}},{"name":"create_dataset","description":"Register training examples from inline rows or an HTTPS URL. Optionally clean each input (HTML, quoted replies, signatures, repeated lines) and combine labels; models trained on them clean and combine live calls the same way. Returns data-quality issues before anything is trained.","effect":"write","scopes":["datasets:write"],"rest":"POST /v1/datasets","cli":"tmc datasets create ./tickets.csv --text-column message --label-column team","input_schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"format":{"type":"string","enum":["csv","jsonl","json"]},"text_column":{"description":"For display: the file column the input came from.","type":"string"},"label_column":{"description":"For display: the file column the answers came from; null when there are none.","type":["string","null"]},"source_url":{"description":"HTTPS URL we can fetch the file from (signed URLs welcome).","type":"string","format":"uri"},"incumbent":{"description":"Who gave the examples' `incumbent` answers. Named \"Your current model\" when omitted.","type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80,"description":"What answers today, e.g. \"Claude Opus 5.5 with our prompt\", \"keyword rules\" or \"the support team\"."},"cost_usd_per_1k":{"description":"Its cost per 1,000 calls, if known. Shown as reported.","type":"number","minimum":0,"maximum":100000},"latency_ms_p95":{"description":"Its p95 latency in ms, if known. Shown as reported.","type":"number","minimum":0,"maximum":600000}},"required":["name"]},"cleaning":{"description":"How to clean each input before storing it. Send the text as it is; cleaning it yourself first is fine too.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","properties":{"markup":{"default":false,"description":"HTML to text: block tags become line breaks; styles, scripts, comments and inline-image placeholders are dropped; entities decoded.","type":"boolean"},"quoted_replies":{"default":false,"description":"Drop quoted earlier messages: `>` lines, and everything from a reply header on (-----Original Message-----, On … wrote:, From:/Sent:/To: blocks, and the same in German, French, Spanish, Italian and Dutch).","type":"boolean"},"signatures":{"default":false,"description":"Drop a greeting-only first line and, at the end, a `-- ` signature, a \"Sent from my…\" footer, or a sign-off with the few short lines under it.","type":"boolean"},"drop_lines":{"default":[],"description":"Lines dropped wherever they appear, compared after lowercasing, collapsing spaces and masking email addresses and long numbers.","maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":1000}}},"description":"How one text input is cleaned: when examples are registered, and on every call a model trained on them answers. Cleaning twice changes nothing, and a step that would empty a value leaves it as it was."}},"label_map":{"description":"Labels to combine before storing the examples.","type":"object","propertyNames":{"type":"string","minLength":1,"maxLength":200},"additionalProperties":{"type":"string","minLength":1,"maxLength":60}},"rows":{"description":"Inline examples, { input, output } or the { text, label } shorthand; only what training needs. Provide this or source_url.","maxItems":10000,"type":"array","items":{"anyOf":[{"type":"object","properties":{"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string","maxLength":20000},"description":"Input values keyed by the task's input names. A text input takes a string."},"output":{"default":{},"description":"The right answer, as it turned out. Empty when it isn't known.","type":"object","properties":{"label":{"description":"single_label: the one category.","anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"labels":{"description":"multi_label: every tag that applies; empty when none do.","maxItems":50,"type":"array","items":{"type":"string","maxLength":200}},"fields":{"description":"extraction: each field's value by name; null when the input doesn't have it.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}]}}}},"incumbent":{"description":"What the model in use today answered. Graded against the right answer to compare with ours; never trained on.","type":"object","properties":{"label":{"description":"single_label: the one category.","anyOf":[{"type":"string","maxLength":200},{"type":"null"}]},"labels":{"description":"multi_label: every tag that applies; empty when none do.","maxItems":50,"type":"array","items":{"type":"string","maxLength":200}},"fields":{"description":"extraction: each field's value by name; null when the input doesn't have it.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}]}}}},"at":{"description":"When the call happened (ISO 8601, e.g. 2026-09-14T10:32:00Z). With times, each label's most recent examples are held out for testing.","type":"string","maxLength":40}},"required":["input"]},{"type":"object","properties":{"text":{"type":"string","maxLength":20000},"label":{"anyOf":[{"type":"string","maxLength":200},{"type":"null"}]}},"required":["text","label"],"description":"Shorthand for one text input and one label: the same as { input: { text }, output: { label } }."}]}}},"required":["name","format"]}},{"name":"create_model_job","description":"Start a long-running training job. Requires an idempotency key and a spending cap; returns immediately with a queued job.","effect":"spend","scopes":["jobs:create"],"rest":"POST /v1/jobs","cli":"tmc models create --task … --data ./tickets.csv --max-spend 25 --idempotency-key …","input_schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":80},"task":{"type":"object","properties":{"description":{"type":"string","minLength":8,"maxLength":500,"description":"What the model should do, in plain language."},"template_id":{"type":"string","enum":["support_routing","feedback_classification","invoice_extraction","custom"],"description":"Starting template. `custom` means the task was described from scratch."},"inputs":{"default":[{"name":"text","type":"text"}],"description":"What the model reads. Defaults to one text input named `text`.","minItems":1,"maxItems":10,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,47}$","description":"Key of this input in an example's or a request's `input`, e.g. `subject`."},"type":{"type":"string","enum":["text"],"description":"What an input holds. Text today; images, audio and documents will be added as more types."},"description":{"description":"What the input holds. Helps the general-purpose LLM comparison.","type":"string","maxLength":240}},"required":["name","type"]}},"output_kind":{"type":"string","enum":["single_label","multi_label","extraction"],"description":"single_label: pick exactly one category. multi_label: pick any number of tags. extraction: return named fields."},"labels":{"default":[],"maxItems":50,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":60,"description":"Category the model can return."},"description":{"description":"Plain-language definition; improves training.","type":"string","maxLength":240},"metadata":{"type":"object","propertyNames":{"type":"string","minLength":1,"maxLength":40},"additionalProperties":{"type":"string","maxLength":200},"description":"Returned with a prediction of this label: anything your code acts on, such as a queue, an owner or an action."}},"required":["name"]}},"fields":{"default":[],"description":"extraction: the fields to return.","maxItems":30,"type":"array","items":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-z][a-z0-9_]{0,47}$","description":"Field key in the model's output, e.g. `invoice_number`."},"type":{"type":"string","enum":["text","number","date","money"]},"description":{"type":"string","maxLength":240}},"required":["name","type"]}}},"required":["description","template_id","output_kind"]},"dataset_id":{"type":"string","minLength":1,"description":"A dataset registered with POST /v1/datasets (or a bundled sample id)."},"requirements":{"type":"object","properties":{"min_accuracy":{"type":"number","minimum":0.5,"maximum":0.995,"description":"Quality bar on the held-out test set (0–1)."},"max_latency_ms":{"type":"integer","minimum":1,"maximum":10000,"description":"p95 latency budget per prediction, in ms."},"deployment":{"type":"string","enum":["hosted_api","self_hosted","on_device"],"description":"Where the finished model will run. Affects candidate selection and packaging."},"max_spend_usd":{"type":"number","minimum":0,"maximum":10000,"description":"Hard spending cap for this job. The job stops before exceeding it."},"third_party":{"default":null,"description":"Null (the default): only models that run on our servers can be chosen. Set: models that call a third-party model can be tried, chosen and deployed too. Separate from `compare_with_llms`, which only scores general-purpose LLMs for comparison.","anyOf":[{"type":"object","properties":{"daily_cap_usd":{"type":"number","minimum":0.01,"maximum":10000,"description":"Live calls to such a model stop for the day once they've cost this much, and callers fall back to the model they use today."}},"required":["daily_cap_usd"],"description":"Allows models that call a third-party model, such as Claude through Anthropic's API, for every answer. If one is chosen and deployed, every live call goes to that provider, with emails and long numbers masked."},{"type":"null"}]}},"required":["min_accuracy","max_latency_ms","deployment","max_spend_usd"]},"advanced":{"type":"object","properties":{"base_models":{"anyOf":[{"type":"string","const":"auto"},{"minItems":1,"type":"array","items":{"type":"string"}}],"description":"`auto` lets the planner choose candidates; or pin specific base models."},"max_candidates":{"type":"integer","minimum":1,"maximum":8},"validation_split":{"type":"number","minimum":0.05,"maximum":0.4},"epochs":{"anyOf":[{"type":"string","const":"auto"},{"type":"integer","minimum":1,"maximum":50}]},"learning_rate":{"anyOf":[{"type":"string","const":"auto"},{"type":"number","exclusiveMinimum":0,"maximum":1}]},"quantization":{"type":"string","enum":["none","int8","int4"]},"early_stopping":{"type":"boolean"},"seed":{"type":"integer","minimum":0,"maximum":2147483647}},"required":["base_models","max_candidates","validation_split","epochs","learning_rate","quantization","early_stopping","seed"]},"mode":{"type":"string","enum":["simulated","training"],"description":"`simulated` replays a scripted run with illustrative results and costs nothing. `training` trains a real model."},"compare_with_llms":{"description":"Also score general-purpose LLMs on the same held-out examples, which sends those examples to the LLM provider. Defaults to true. Counts toward max_spend_usd.","type":"boolean"},"swap_bar":{"description":"When the examples include the current model's answers: what ours must show to be ready to replace it. Defaults to at_least_as_good with a 2-point margin.","type":"object","properties":{"rule":{"type":"string","enum":["at_least_as_good","better"],"description":"at_least_as_good: not behind the current model by more than `margin`, with 95% confidence. better: ahead of it, with 95% confidence."},"margin":{"type":"number","minimum":0,"maximum":0.2,"description":"How far behind still counts as as good, as a share of calls (0.02 = 2 points). No label or field may trail by more than this either."}},"required":["rule","margin"]},"idempotency_key":{"type":"string","minLength":8,"maxLength":128,"description":"Reuse the same key when retrying. Same key + same body returns the original job; a different body is rejected."},"approval_token":{"description":"Only needed when the estimate exceeds the key's auto-approve limit.","type":"string"}},"required":["name","task","dataset_id","requirements","mode","idempotency_key"]}},{"name":"get_job","description":"Poll a job. Honour the Retry-After hint; terminal states are succeeded, failed and canceled.","effect":"read","scopes":["models:read"],"rest":"GET /v1/jobs/{job_id}","cli":"tmc jobs watch job_…","input_schema":{"type":"object","properties":{"job_id":{"type":"string"}},"required":["job_id"]}},{"name":"cancel_job","description":"Stop a running job. Nothing more is charged. Canceling a finished job returns 409.","effect":"write","scopes":["jobs:cancel"],"rest":"POST /v1/jobs/{job_id}/cancel","cli":"tmc jobs cancel job_…","input_schema":{"type":"object","properties":{"job_id":{"type":"string"}},"required":["job_id"]}},{"name":"get_model","description":"Metrics on held-out data, requirement checks, per-label scores, confusion matrix and baseline comparison.","effect":"read","scopes":["models:read"],"rest":"GET /v1/models/{model_id}","cli":"tmc models show mdl_…","input_schema":{"type":"object","properties":{"model_id":{"type":"string"}},"required":["model_id"]}},{"name":"predict","description":"Run the model on one input. Hosted models bill per call at the price in the model's metrics.","effect":"spend","scopes":["models:predict"],"rest":"POST /v1/models/{model_id}/predict","cli":"tmc predict mdl_… \"Where is my package?\"","input_schema":{"anyOf":[{"type":"object","properties":{"model_id":{"type":"string"},"input":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string","maxLength":20000},"description":"Values keyed by the task's input names, as they are: the model cleans them the way its examples were cleaned."}},"required":["model_id","input"]},{"type":"object","properties":{"model_id":{"type":"string"},"text":{"type":"string","minLength":1,"maxLength":20000,"description":"Shorthand for a task with one text input."}},"required":["model_id","text"]}]}},{"name":"export_model","description":"Get a short-lived signed download for an artifact, with its SHA-256 and model card.","effect":"write","scopes":["models:export"],"rest":"POST /v1/models/{model_id}/exports","cli":"tmc models export mdl_… --format onnx --out ./model.onnx","input_schema":{"type":"object","properties":{"model_id":{"type":"string"},"format":{"type":"string","enum":["onnx","safetensors","gguf","docker","hosted_endpoint"]}},"required":["model_id","format"]}}]}