DEVELOPER GUIDE
AI Model Trainer API
Connect the models you train to the tools you use.
Classify from your own tools
Call the models you train in StayCharted AI Model Trainer from your own code, a spreadsheet, or an automation tool.
Base URL https://amt.staycharted.com
You train a model in the app by uploading examples you've already labelled. Then send it new text, or links to pictures, and get back a category and a confidence. Three endpoints cover everything:
| Endpoint | Use it for |
|---|---|
GET /api/v1/models |
Listing the models you can call, and testing a key |
POST /api/v1/models/{id}/classify |
One record in, one flat answer out. Best for Zapier, Make, Power Automate, Salesforce and spreadsheets |
POST /api/v1/models/{id}/predict |
One record, or up to 100 at once. Best for your own code |
The API is included on Essentials and Business. See Pricing.
Get started in three steps
1. Create a key. Sign in to AI Model Trainer, open Settings → API keys, and create one.
- The full key (it starts with
amt_live_) is shown once. Store it then. - Up to ten keys can be active at once. Give each integration its own, so you can revoke one without breaking the others.
2. Find your model's id. It's listed on the same screen. Or call
GET /api/v1/models, which also confirms the key works and costs nothing.
Only published models can be called.
3. Classify something.
curl -sS https://amt.staycharted.com/api/v1/models/$MODEL_ID/classify \
-H "Authorization: Bearer $STAYCHARTED_KEY" \
-H 'Content-Type: application/json' \
-d '{"input":"My invoice charged me twice this month"}'
{
"label": "Billing",
"confidence": 0.94,
"modelId": "8f14e45f-ceea-467a-9b2c-1d5c2b0e7a11",
"modelName": "Support tickets"
}
label is the answer. confidence runs from 0 to 1.
What it costs, and the one limit to design around
Every record is one prediction, drawn from the same monthly allowance your file fills in the app use. A request that is refused, for any reason below, costs nothing.
The rate limit is counted in records per minute, for the whole workspace: 30 a minute on Essentials, 60 on Business.
- Every key draws on that one allowance, so adding keys doesn't add throughput.
- The allowance refills continuously; it doesn't reset on the minute.
- A request that doesn't fit is refused whole with
429and aRetry-Afterheader, and nothing is charged.
So the API is for a steady trickle, not a flood. It suits a ticket as it arrives, a row as it's created, a form as it's submitted. For thousands of rows, upload the file in the app instead. It runs in one pass on hardware sized for the job, and you download the filled file.
Recipes
Google Sheets: =CLASSIFY(A2)
In your sheet: Extensions → Apps Script, paste this, save, and go back to the sheet.
const STAYCHARTED_KEY = 'amt_live_...'; // your API key
const STAYCHARTED_MODEL = '8f14e45f-...'; // your model id
/**
* Classify one cell.
* @param {string} text The cell to classify.
* @return {string} The label.
* @customfunction
*/
function CLASSIFY(text) {
if (!text || !String(text).trim()) return '';
const url = 'https://amt.staycharted.com/api/v1/models/'
+ STAYCHARTED_MODEL + '/classify';
// Two attempts: a column of formulas recalculates all at once and the
// per-minute allowance is shared. Retry-After says exactly how long to wait.
for (let attempt = 0; attempt < 2; attempt++) {
const res = UrlFetchApp.fetch(url, {
method: 'post',
contentType: 'application/json',
headers: { Authorization: 'Bearer ' + STAYCHARTED_KEY },
payload: JSON.stringify({ input: String(text) }),
muteHttpExceptions: true,
});
const code = res.getResponseCode();
if (code === 200) return JSON.parse(res.getContentText()).label || '';
if (code === 429 && attempt === 0) {
const wait = Number(res.getAllHeaders()['Retry-After'] || 5);
Utilities.sleep(Math.min(wait, 25) * 1000);
continue;
}
// Show the reason rather than an empty cell.
return 'ERR ' + code + ': ' + (JSON.parse(res.getContentText()).code || '');
}
return 'ERR rate limited';
}
Then type =CLASSIFY(A2) in a cell and drag it down.
- Start with a few rows at a time. Sheets
recalculates the whole column at once. Large selections can exceed the shared rate limit and return
ERR rate limited. For more than a few hundred rows, fill the file in the app instead. - The key travels with the sheet. Anyone you share the sheet with can read the script. Give it its own key, and revoke the key when you're done.
Zapier
Add a Webhooks by Zapier → Custom Request action.
| Field | Value |
|---|---|
| Method | POST |
| URL | https://amt.staycharted.com/api/v1/models/<MODEL_ID>/classify |
| Data Pass-Through? | no |
| Data | {"input": "<the field from your trigger>"} |
| Headers | Authorization: Bearer <YOUR_API_KEY> and Content-Type: application/json |
The next step can use label, confidence, modelId and modelName as
plain fields. Zapier counts one task per action, so a thousand records is
a thousand tasks.
Make
Add HTTP → Make a request.
- URL:
https://amt.staycharted.com/api/v1/models/<MODEL_ID>/classify - Method:
POST - Headers:
Authorization=Bearer <YOUR_API_KEY> - Body type:
Raw, content typeapplication/json - Request content:
{"input": "{{yourField}}"} - Parse response: yes. Without it, the next module sees a string
instead of
label.
Your own code: /predict for batches
Send up to 100 records in one call. Answers come back in the order sent, each with its input echoed.
curl -sS https://amt.staycharted.com/api/v1/models/$MODEL_ID/predict \
-H "Authorization: Bearer $STAYCHARTED_KEY" \
-H 'Content-Type: application/json' \
-d '{"inputs":["My invoice charged me twice this month","The parcel arrived with a cracked screen"]}'
{
"model": { "id": "8f14e45f-ceea-467a-9b2c-1d5c2b0e7a11", "name": "Support tickets" },
"predictions": [
{ "input": "My invoice charged me twice this month", "label": "Billing", "confidence": 0.94 },
{ "input": "The parcel arrived with a cracked screen", "label": "Shipping", "confidence": 0.71 }
],
"usage": { "predictions": 2 }
}
The whole request is checked against your remaining monthly allowance before any of it runs. A request that would go over is refused entirely, never half-answered.
AI assistants
To use your models from Claude or ChatGPT instead of code, connect the assistant instead. See Use AI Model Trainer from Claude and ChatGPT.
Reference
Authentication
Send your key in every request:
Authorization: Bearer amt_live_…
Keys are created and revoked only in the app, never through the API. A key that could create keys couldn't really be revoked.
GET /api/v1/models
Lists the models this key can call. A model appears only when it is published, switched on, and allowed by your plan. This call spends no predictions and has its own allowance of 60 requests a minute.
{
"models": [
{ "id": "8f14e45f-ceea-467a-9b2c-1d5c2b0e7a11", "name": "Support tickets", "custom": true },
{ "id": "3c59dc04-8e88-4950-9f2b-7a1c2a1e0b02", "name": "Product feedback", "custom": false }
]
}
custom is true for a Dedicated AI Model. It is false for a Classifier
Model or an AI Classifier Model. All of them are called the same way.
POST /api/v1/models/{id}/classify
One record in, one flat answer out.
| Request field | Type | Notes |
|---|---|---|
input |
string, or object | Text model: the text. Picture model: a link to the picture (http or https), or {"imageBase64": "..."} for a picture under 1.5 MB. |
| Response field | Type | Notes |
|---|---|---|
label |
string or null | The category. |
confidence |
number or null | 0 to 1. |
modelId |
string | |
modelName |
string | |
error |
string | Picture models only, when the picture couldn't be used. See below. |
message |
string | The same reason in plain words. |
POST /api/v1/models/{id}/predict
Exactly one of:
| Request field | Type | Notes |
|---|---|---|
input |
string | One record. |
inputs |
array | 1 to 100 records. For a picture model: up to 20 pictures, each a link or {"imageBase64": "..."}. |
| Response field | Type | Notes |
|---|---|---|
model.id, model.name |
string | |
predictions[] |
array | One per input, in order: input, label, confidence, and for a picture that couldn't be used, error and message. |
usage.predictions |
integer | Records counted against this month's allowance. |
predictions is always an array, even for a single input.
Pictures
For a model that reads pictures:
- Each picture counts once against your monthly picture allowance, as well as once as a prediction.
- A picture that can't be used comes back with
labelandconfidenceset to null, plus anerrorcode and amessage, and isn't counted. The codes are:not_found,forbidden,unreachable,timeout,too_large,web_page,not_an_image,unsupported_format,too_small,corrupt,blocked_address,not_a_link. - Accepted formats: JPEG, PNG, WebP, GIF, BMP and TIFF.
Rate-limit headers
| Header | Meaning |
|---|---|
X-RateLimit-Limit |
Records a minute your plan allows, for the whole workspace. |
X-RateLimit-Remaining |
Records you can send right now. |
Retry-After |
On a 429: seconds until this request will fit. A smaller request may fit sooner. |
Errors
Every error is JSON with a stable code. Branch on code, never on the
error sentence, which is written for people and may be reworded.
A refusal caused by your plan also carries entitlement, limit, used,
resetsAt and upgrade. Those let a client tell "wait until the month
resets" from "this plan will never allow it".
| HTTP | code |
What it means, and what to do |
|---|---|---|
| 400 | api.input_missing |
classify got no usable input. It takes one string; an array is /predict's shape. |
| 400 | api.input_required |
/predict needs input (one string) or inputs (an array). |
| 400 | api.input_invalid |
Every input must be a non-empty string. |
| 400 | api.too_many_inputs |
More than 100 records in one request. Send several requests, or fill the file in the app. |
| 400 | api.too_many_images |
More than 20 pictures in one request. |
| 400 | api.text_input_invalid |
This model reads text: send each input as a string. |
| 400 | api.image_input_invalid |
This model reads pictures: send a link or {"imageBase64": "..."}. |
| 400 | api.image_too_large |
A picture sent in the request is over 1.5 MB. Send a link instead. |
| 401 | api.key_missing |
No Authorization: Bearer <key> header. |
| 401 | api.key_invalid |
The key is wrong or revoked. Create a new one. |
| 402 | plan.feature_not_included |
The API isn't on this plan. Retrying won't help; upgrade names the plan that includes it. |
| 402 | plan.prediction_limit |
This month's predictions are used up. resetsAt says when they come back. |
| 403 | account.suspended |
Contact support. |
| 404 | api.model_not_found |
Wrong model id, or the model is in another workspace. |
| 409 | api.model_not_deployed |
The model is trained but not published. Publish it in the app. |
| 409 | api.model_needs_retraining |
The model must be retrained before it can answer again. Its owner retrains it in the app. |
| 429 | api.rate_limited |
Over the per-minute allowance. Wait Retry-After seconds. |
| 503 | api.model_unavailable |
A service the model needs is briefly unavailable, usually during a restart. Retry after a short wait. Nothing was charged. |
Need help connecting?
Contact support@staycharted.com.
Get an API key →