# Limits, errors and cost

## Hard limits

| Limit | Value |
| --- | --- |
| Objects per read | 6 |
| Shopping matches per object | 10 |
| Image size | 10 MB |
| Image formats | JPG, PNG, WEBP, GIF |
| `focus` length | 160 characters, control characters stripped |
| Trial allowance | 10 requests |
| Trial lifetime | 24 hours from issue |
| Key mints per address per day | 5 |
| Key mints per network per day | 10 |
| Key mint rate | 3 per minute |
| Concurrent visual searches inside one read | 2 |
| Extractions inside one read | strictly one at a time |

A read is minutes, not seconds. One vision call, then one extraction per object
run sequentially, with each object's search starting as soon as its extraction
lands. Six objects is the slowest case.

## How a request is charged

Charged when a read **starts**, so ten concurrent calls cannot all pass on one
remaining request — the guard is in the SQL update, not in application code.

Given back only when the platform itself failed. Not given back for:

- a photo with nothing clearly enough visible to shop for (that is a successful
  read that happens to be empty),
- a malformed request of your own,
- a read you abandon without collecting.

Polling is free, over HTTP and over MCP. Key status and revocation are free.
`list_focus_domains` and `GET /api/v1/domains` are free and need no key.

## HTTP errors

| Status | `code` | Means |
| --- | --- | --- |
| 400 | — | Bad multipart body, or an unknown `domain`. |
| 401 | `missing_api_key` | No `x-api-key` and no bearer token. |
| 401 | `invalid_api_key` | The key is not one of ours. |
| 401 | `revoked_api_key` | You or a later mint revoked it. |
| 401 | `expired_api_key` | Past its 24 hours. Mint another. |
| 404 | — | No such read, or a read belonging to another key. |
| 405 | — | Wrong method. `GET /mcp` is one of these. |
| 413 | — | Over the size ceiling. |
| 429 | `quota_exhausted` | The key's included requests are used up. |
| 429 | — | Key minting rate limit. |
| 503 | `api_not_configured` | The deployment has no way to authenticate. |
| 503 | — | Search credentials or the durable job store are missing. |

A `404` is deliberately indistinguishable between "no such read" and "not
yours": a request id is a UUID and confirming one exists would leak it.

## Reading an empty result

`items: []` with a `summary` naming the domain is a real answer, not a failure.
The reader is instructed to return an empty list rather than invent an object it
cannot see, which is the behaviour you want and the one that still costs a
request.

## What is stored, and for how long

- The uploaded photograph, each cutout, and the finished result live in
  Cloudflare R2 for the life of the job.
- Cutouts are served from `/api/inspire/jobs/{id}/assets/piece-N.webp`, an
  unguessable path, cached privately.
- API keys are stored only as a SHA-256 digest. The plaintext exists in one
  email and nowhere else.
- The email address a key was minted against, and the network it was minted
  from, are stored only as keyed digests — they enforce the daily mint caps and
  cannot be reversed into a mailing list.
- Requests made with a key are not attributed to a person beyond that digest.

## Status

`https://inspired.jetskibay.com/status.json` reports the platform's own view of
its components in the Jetskibay status feed shape. A report older than five
minutes is treated as unknown by the catalogue, and a missing feed never means
operational.

## Dependencies you are inheriting

OpenAI for reading, Pruna `p-image-edit` for extraction, SerpAPI's Google Lens
in product mode for search, with Google Reverse Image as the fallback when Lens
returns too little. An outage or a rate limit at any of them surfaces as a failed
read, which is refunded.
