Skip to main content
For AI agents and builders

Give your agent the photographs you already own.

Tagrly is a JSON API over a business's own photo library. Describe the page you are building and it returns that business's images with alt text rewritten for the topic. One endpoint takes no account at all: POST /api/try/analyze-photo reads a single photo you upload and returns the full structured analysis.

On this page

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.

What the API is and is not.
Tagrly isTagrly 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"
The whole call. No header, no key, no signup.
{
  "ok": true,
  "vertical": "restaurant-bar",
  "prompt_version": "restaurant-bar-v3",
  "analysis": { "alt_text": "...", "scene": "...", ... },
  "elapsed_ms": 6142,
  "remaining": 4,
  "limit": 5
}
A 200 response. remaining and limit report your rate-limit budget so an agent can pace itself without guessing.
Limit

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.

Errors from POST /api/try/analyze-photo.
Status error What to do
400no-fileNothing arrived under photo. Check the field name.
400empty-fileZero bytes. Re-read the file.
400wrong-typeDecoded, but not a supported format. The message names what it was.
400bad-imageWould not decode at all. Try a different photo.
413too-largeOver 8 MB. Re-export smaller.
429rate-limitWait retry_after seconds. It is returned in the body.
500server-misconfiguredOurs, not yours. Nothing to retry against.
502parse-failed · upstreamThe 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.

Base analysis fields, present for every photo.
GroupFields
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.

  1. Decide which section of the page you are filling.
  2. POST /api/page with a plain-English topic for it.
  3. Render each pick's hosted_url and alt_rewrite.
  4. POST /api/usage with the ids you actually rendered.
  5. Pass exclude_recently_used_days next time, so a long automation never recycles the same hero.
Tip

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.

Base URL https://tagrly.com. Every keyed call is scoped to the workspace the key unlocks.
If you want to… Call Key
Read one photo you uploadPOST /api/try/analyze-photoNo
Fill a page section from a plain topicPOST /api/pageYes
Define the slots yourselfPOST /api/briefYes
Log the images you renderedPOST /api/usageYes
List what this workspace logged latelyGET /api/usage/recentYes
Run a keyword and facet searchGET /api/searchYes
Get one image's full analysisGET /api/image/{drive_id}Yes
List the workspace's collectionsGET /api/collectionsYes
Save a set for a human to reviewPOST /api/add-to-collectionYes
Resolve image ids to source URLsGET /api/urlsYes
Get every id matching a searchGET /api/match-idsYes
List workspaces this key reachesGET /api/workspacesYes

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>
One header on every request. The workspace slug rides in the visible key so your own logs show which tenant a call belonged to.

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
One topic in, a page-ready set of images out.

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"]
  }
}
A tool definition for any tool-use loop. Your handler POSTs the arguments straight through to /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"
Claude Code setup in one command. Setup for every other client is on the MCP server page.

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.

Enforced limits.
Limit Default Ceiling
Anonymous photo reads, per hour, per connection55
Anonymous upload size8 MB8 MB
/api/search results per call24500
/api/usage items per POSTn/a100
/api/usage/recent lookback, days14365
/api/usage/recent items100500
/api/urls ids per calln/a500
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.

If you are an AI agent reading this page

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.

Start with the call that needs nothing.

Send one photo to /api/try/analyze-photo and read what comes back. When you want it pointed at a real library, create a key in Settings.