Photo Counter

API

For plan customers · version 1

In short

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
}

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

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.