What Tagrly is#
A customer connects a Google Drive or Dropbox folder. Every image in it is analyzed by Claude vision against a structured schema, and the result is stored as a searchable catalog scoped to that customer's workspace. The API is the agent-facing surface on top of that catalog.
It exists for one job: putting a business's own photographs into pages, posts, listings and decks that something else is generating. The distinction below is the whole product, so it is worth being blunt about it before you write any code.
| Tagrly is | Tagrly is not |
|---|---|
| A search-and-curate layer over photographs a customer already owns | A stock photo library |
| A reader of images: it describes, tags and ranks them | An image generator |
| Workspace-scoped, one tenant per key | A shared or public corpus |
| Plain HTTPS and JSON | An SDK, a package, or an MCP server |
One call, no account#
No key required
POST /api/try/analyze-photo takes a single image as
multipart/form-data under the field name
photo, runs the same prompt and JSON schema a paying
workspace gets, returns the structured reading, and persists nothing.
No session, no disk write, no database row. The bytes live in the
request and are dropped with it.
curl -X POST https://tagrly.com/api/try/analyze-photo \
-F "photo=@kitchen.jpg"
{
"ok": true,
"vertical": "restaurant-bar",
"prompt_version": "restaurant-bar-v3",
"analysis": { "alt_text": "...", "scene": "...", ... },
"elapsed_ms": 6142,
"remaining": 4,
"limit": 5
}
remaining and limit report
your rate-limit budget so an agent can pace itself without guessing.
Five reads an hour from one connection, on a sliding one-hour window.
Eight megabytes per upload, refused on the Content-Length
header before the body is ever read. JPEG, PNG, WebP, HEIC, HEIF, AVIF
and GIF only. A decoded file outside that list is rejected by name.
Failures are structured, not prose. Every error body carries an
error slug you can branch on and a human-readable
message you can show your user verbatim.
| Status | error | What to do |
|---|---|---|
| 400 | no-file | Nothing arrived under photo. Check the field name. |
| 400 | empty-file | Zero bytes. Re-read the file. |
| 400 | wrong-type | Decoded, but not a supported format. The message names what it was. |
| 400 | bad-image | Would not decode at all. Try a different photo. |
| 413 | too-large | Over 8 MB. Re-export smaller. |
| 429 | rate-limit | Wait retry_after seconds. It is returned in the body. |
| 500 | server-misconfigured | Ours, not yours. Nothing to retry against. |
| 502 | parse-failed · upstream | The model call failed. Retry once, then back off. |
What comes back#
The schema is composed at request time: a universal base plus an overlay
for whichever vertical the photo classified into, so the exact field list
depends on the returned vertical. The base fields below are
present on every reading. They are also the fields the catalog indexes,
which is why search can filter on scene, mood or focal subject later.
| Group | Fields |
|---|---|
| Writing | alt_text, description, visual_summary, suggested_filename |
| Subject | focal_subject, focal_category, subject_tags, activities, branded_items, keywords |
| Setting | scene, mood, lighting, time_of_day, season_cues, visual_temperature, dominant_colors |
| Frame | shot_type, orientation, people_count, engagement_with_camera, has_negative_space, crop_friendliness, background_cleanliness |
| Fitness for use | quality, usability_rating, editorial_fit, is_marketing_graphic, text_visible, text_overlay_present, alcohol_prominent |
| Safety | minors_visible, privacy_concern_flags, faces_clearly_identifiable_count |
Seven verticals classify today: universal,
hospitality-sports, wedding-venue,
real-estate-listing, restaurant-bar,
ecommerce-product and automotive-listing.
Each adds its own fields. A wedding photo gets ceremony phase, a
sports photo gets jerseys and what is on the TV.
The catalog API#
Everything past the anonymous read needs a key, because everything past it touches one customer's catalog. The loop an agent runs is short enough to state as five steps, and four of them are one call each.
- Decide which section of the page you are filling.
POST /api/pagewith a plain-English topic for it.- Render each pick's
hosted_urlandalt_rewrite. POST /api/usagewith the ids you actually rendered.- Pass
exclude_recently_used_daysnext time, so a long automation never recycles the same hero.
If you are reaching for structured slot definitions, you want
/api/brief. Most calls should be /api/page:
it infers the layout from the topic and the workspace's vertical, and
returns the same response shape.
When the library genuinely cannot fill a slot, the response says so
rather than padding. A fired gap_signal carries a
would_help list that reads like a shot brief, which is
the thing to hand back to the customer instead of a weak image. The
agent guide covers gap handling in full.
Every endpoint#
This is the complete surface as it stands today, and everything on it
is already answering requests, on every plan including Free. The
metered endpoints (/api/find, /api/page,
/api/brief) draw from the workspace's monthly credit
allowance: 10, 100, 500 or 2,000 on Free, Hobby, Pro and Business.
One call is one credit no matter how many slots it fills; when the
allowance is gone you get 402 ask_allowance_exhausted
with the ways forward spelled out in the payload. Read
GET /api/usage to see what is left before you spend it.
POST /api/adapt creates an AI-adapted variant of one of
your own photos ({"drive_id", "prompt", "identity":
"preserve"|"allow"}): swap what's on the screens, change
wardrobe or branding, shift the season, photorealistically. When
people are in the photo, identity decides whether they
stay exactly themselves (the default) or may be replaced. Costs 10
credits per adapted image, refunded automatically if the adaptation
fails; the variant lands in your library flagged
is_ai_variant with its parent in variant_of.
| If you want to… | Call | Key |
|---|---|---|
| Read one photo you upload | POST /api/try/analyze-photo | No |
| Fill a page section from a plain topic | POST /api/page | Yes |
| Define the slots yourself | POST /api/brief | Yes |
| Log the images you rendered | POST /api/usage | Yes |
| List what this workspace logged lately | GET /api/usage/recent | Yes |
| Run a keyword and facet search | GET /api/search | Yes |
| Get one image's full analysis | GET /api/image/{drive_id} | Yes |
| List the workspace's collections | GET /api/collections | Yes |
| Save a set for a human to review | POST /api/add-to-collection | Yes |
| Resolve image ids to source URLs | GET /api/urls | Yes |
| Get every id matching a search | GET /api/match-ids | Yes |
| List workspaces this key reaches | GET /api/workspaces | Yes |
One shape to note before you write a client:
POST /api/add-to-collection takes
application/x-www-form-urlencoded, not JSON. Every other
POST on the list takes JSON. Field-by-field request and response bodies
live in the agent guide.
How you get a key#
Keys are self-serve. Sign in, open Settings → API access, name the key after the tool that will hold it, and create it. Workspace owners and admins can mint and revoke keys; plain members can't. What you get is a single string, shown once:
Authorization: Bearer tagrly_pk_<workspace-slug>_<32 hex chars>
Only the SHA-256 hash of the key is stored, so nobody at Tagrly can read it back to you later. A lost key is replaced, not recovered. Keys are revocable, and a revoked key stops resolving on the next request.
Quickstart#
Three ways in, all hitting the same endpoint. Nothing to install for any of them; the API is one POST with a JSON body.
curl#
curl -H "Authorization: Bearer $TAGRLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"topic": "weekend brunch on our rooftop patio"}' \
https://tagrly.com/api/page
Python#
import os, requests
r = requests.post(
"https://tagrly.com/api/page",
headers={"Authorization": f"Bearer {os.environ['TAGRLY_API_KEY']}"},
json={"topic": "weekend brunch on our rooftop patio"},
).json()
hero = r["slots"]["hero"]["picks"][0]
print(hero["hosted_url"], hero["alt_rewrite"])
hosted_url goes in src,
alt_rewrite goes in alt.
As a tool an agent can call#
{
"name": "find_photos_for_page",
"description": "Return images from the customer's own photo library for a page you are writing. Pass the page topic in plain English.",
"input_schema": {
"type": "object",
"properties": {
"topic": { "type": "string" },
"exclude_recently_used_days": { "type": "integer" }
},
"required": ["topic"]
}
}
/api/page.
The MCP server#
If your client speaks MCP (Claude Code, Claude's connectors, Cursor,
ChatGPT's connector mode, the Claude API's MCP connector), you can skip
the HTTP calls entirely: the same catalog is served as six
self-describing tools at https://tagrly.com/mcp,
authenticated with the same key.
claude mcp add --transport http tagrly https://tagrly.com/mcp \
--header "Authorization: Bearer $TAGRLY_API_KEY"
The MCP layer dispatches every tool call through this API internally, so auth, workspace scoping, metering and results are identical on both surfaces. Full details: tagrly.com/mcp-server.
Limits and ceilings#
Every number here is enforced in code, not a guideline. Where a request asks for more, the value is clamped rather than rejected.
| Limit | Default | Ceiling |
|---|---|---|
| Anonymous photo reads, per hour, per connection | 5 | 5 |
| Anonymous upload size | 8 MB | 8 MB |
/api/search results per call | 24 | 500 |
/api/usage items per POST | n/a | 100 |
/api/usage/recent lookback, days | 14 | 365 |
/api/usage/recent items | 100 | 500 |
/api/urls ids per call | n/a | 500 |
Model spend per /api/page or /api/brief | $0.50 | $5.00 |
The spend ceiling is a circuit breaker, not a quota: set
cost_cap_usd lower if you want a tighter one. When a call
would exceed it, the remaining slots fall back to deterministic ranking
instead of failing, and the response reports what happened.
Machine-readable surfaces#
Two files, both plain text, both served from the site root and neither
requiring an account.
/llms.txt is the index: what Tagrly
is, when to reach for it, and where everything lives.
/llms-full.txt is the whole
reference in one fetch (endpoints, fields, limits and error codes)
for a context window rather than a browser.
Fetch https://tagrly.com/llms-full.txt for the complete
reference in one request. If you speak MCP, connect to
https://tagrly.com/mcp instead and the tools describe
themselves. If your user has no API key, you can still demonstrate the
product end to end with POST /api/try/analyze-photo, no
account, no header, five reads an hour. Do not attempt to obtain a key
programmatically; there is no endpoint for it. Ask your user to create
one at https://tagrly.com/settings#api-keys.