API
For plan customers · version 1
In short
- Send a photo and 1 to 3 example outlines; get back every matching object with its outline.
- It's the same Smart AI count as on the website, with the same credits: a typical photo uses 1 credit.
- You need an access key from a monthly plan. Ask for one.
- For a folder of photos, use our batch script instead of writing code (see the end of this page).
Address and key
All requests go to https://afterthought-app-2026--tally-count-api.us-east.modal.run
with your key in the header Authorization: Bearer tk_…. Bodies are JSON. Keep the key on your
server, not in a web page or app that other people can open: whoever has it can spend its credits.
POST /count
Counts the objects in one photo and charges its credits. Request:
{
"image": "<base64 JPEG, PNG or WebP>",
"examples": [[[x, y], [x, y], [x, y], ...]], // 1 to 3 outlines, in the photo's pixels
"area": [[x, y], ...], // optional: count only inside this outline
"params": {"sizeTol": 0.3, "minScore": 0.5} // optional, see below
}
- examples: polygons of 3 to 400 points around typical objects, one per kind (for example a small and a large pill). A rectangle (4 points) around the object works well.
- area: leave it out to count the whole photo.
- sizeTol: how much bigger or smaller than the examples an object may be: 0.3 is ±30%, at most 2. Default 0.1.
- minScore: how sure the model must be, 0 to 1. Default 0.5; lower finds more, with more mistakes.
- Photos larger than 4096 pixels on the long side are reduced to that size before counting. Sending them at 2400 pixels is faster and gives the same result for most photos. Keep requests under 20 MB.
Answer (shortened):
{
"detections": [
{"x": 812.4, "y": 447.0, "w": 108.2, "h": 45.1, "angle": 3.2, "score": 0.91,
"example_index": 0, "polygon": [[x, y], ...], "negative": false, "lookalike": false},
...
],
"image_size": [1600, 900],
"billing": {"charged": 1, "tiles": 4, "source": "credits", "balance": 1999, "ref": "c_..."}
}
The count is the number of detections where negative and lookalike are both false.
Positions are in the pixels of the photo you sent; example_index says which example an object
matches.
POST /quote
Same body as /count. Returns what the count would cost, without counting or charging:
{"credits": 1, "tiles": 4, "balance": 2000}.
GET /balance
Returns the key's credits: {"credits": 2000, "key_valid": true, ...}.
Credits, limits and errors
- Cost. One credit covers up to 10 image tiles. The number of tiles depends on how small the
objects are compared with the photo, so a typical photo needs 1 credit and a photo of very small objects up to
10. A photo needing more than 100 tiles is refused (
422 too_small): send a count area or a closer photo. - Monthly credits. Your plan's credits are filled up at the start of each month. Unused plan credits don't carry over; credits bought on top do.
- Rate limits. Plan keys can usually count 30 photos a minute and 3000 a day (your plan may differ).
Above that you get
429with aRetry-Afterheader in seconds. - Failed counts aren't charged. On
502,503or504nothing was charged; wait a little and send the photo again. The first count after a quiet period can take up to a minute while the server starts. - Errors are JSON:
{"error": "payment_required", "message": "…"}. Common ones:400 bad_request,402 payment_required(not enough credits),422 too_small,429 rate_limited.
Example
Python, with the requests package:
import base64, requests
API = "https://afterthought-app-2026--tally-count-api.us-east.modal.run"
KEY = "tk_..." # from your plan
with open("tray.jpg", "rb") as f:
image = base64.b64encode(f.read()).decode()
body = {
"image": image,
"examples": [[[410, 220], [470, 220], [470, 262], [410, 262]]], # one bolt
"params": {"sizeTol": 0.3},
}
r = requests.post(API + "/count", json=body, headers={"Authorization": "Bearer " + KEY}, timeout=180)
r.raise_for_status()
found = [d for d in r.json()["detections"] if not d["negative"] and not d["lookalike"]]
print(len(found), "counted")
Batch: a folder of photos
Our script photo_counter_batch.py counts a whole folder: it prices every photo first, shows the
total, then counts them one by one and writes results.csv with a marked image per photo. Outline the
examples once for a fixed camera, or per photo. We send the script with your plan key; it needs Python 3.9 or
newer and pip install pillow certifi.
python photo_counter_batch.py photos/ --examples examples.json --key tk_... --size-tol 0.3
Prefer not to run anything? Send us the photos and we run the batch for you.
Your photos
Photos sent to the API are handled exactly as on the website: counted in memory and not kept by us. See Privacy and the terms of use.