Inspired

Overview

Inspired takes an image, finds every separately purchasable object in it, cuts each one out as its own product shot, and returns live retail listings for each. The subject is a parameter: clothing, furniture, devices, beauty, or anything you name.

The base URL is https://inspired.jetskibay.com/api/v1. Every response is JSON. Requests that carry an image are multipart/form-data; everything else is application/json.

Two endpoints do the same work. POST /jobs starts a durable read and returns immediately with something to poll — use it by default, because a full read takes minutes. POST /inspire holds the connection open until the read is finished, which is simpler but fails on any client or proxy with a short timeout.

Keys

Keys are minted per email address and mailed to it. A trial key works for a fixed number of requests and expires 24 hours after it is issued. Only a hash of the key is stored, so the email is the one place it exists.

POST/api/v1/keys

request
curl -X POST https://inspired.jetskibay.com/api/v1/keys \
  -H "content-type: application/json" \
  -d '{"email":"you@company.com"}'
202 accepted
{
  "status": "sent",
  "message": "Your API key is on its way to you@company.com.",
  "key": {
    "keyPrefix": "isk_9f2c41a…",
    "tier": "trial",
    "requestLimit": 10,
    "requestsRemaining": 10,
    "expiresAt": "2026-08-31T09:12:44.000Z"
  }
}

The key itself is never in this response. Requesting a second key for an address that already has a live one revokes the first and carries the remaining allowance over, so re-requesting is safe and does not reset the quota.

Authentication

Send the key on every request, either way:

headers
x-api-key: isk_9f2c41a8b03d5e6712c4f890
# or
Authorization: Bearer isk_9f2c41a8b03d5e6712c4f890

A missing or unusable credential returns 401; an exhausted one returns 429. Metered responses carry x-inspired-requests-remaining.

Focus domains

The domain field tells the reader what kind of object to look for. It changes the vision prompt, the JSON schema the model must fill, and the extraction instruction. Omitting it means fashion.

DomainWhat it returnsAliases
fashionGarments, shoes, bags, jewellery worn by a person. Layers separated.clothing, clothes, outfit, apparel, style
furnitureSofas, chairs, tables, lighting, rugs, shelving, decor.interior, home
devicesPhones, laptops, audio, cameras, peripherals, appliances.tech, electronics
beautySkincare, makeup, fragrance, haircare, tools.—
everythingEvery distinctly purchasable object, no category filter.any, all

Add focus — up to 160 characters of plain text — to narrow any domain to one thing: domain=everything with focus=kitchen appliances only. It is appended to the reader's instruction, not to the search query.

Start a request

POST/api/v1/jobs

Starts a durable read and returns immediately. This is the endpoint to build on. Charges one request against your key.

FieldTypeNotes
imagefileJPG, PNG, WEBP, or GIF up to 10 MB. photo is accepted as an alias.
pinterestUrlstringA public Pinterest pin URL, instead of image.
domainstringOne of the focus domains. Defaults to fashion.
focusstringOptional free-text narrowing, up to 160 characters.
request
curl -X POST https://inspired.jetskibay.com/api/v1/jobs \
  -H "x-api-key: $INSPIRED_KEY" \
  -F "image=@living-room.jpg" \
  -F "domain=furniture"
202 accepted
{
  "requestId": "6f1c9b2e-4d3a-4f77-9a01-2b8f6c0d51aa",
  "status": "queued",
  "statusUrl": "https://inspired.jetskibay.com/api/v1/jobs/6f1c9b2e-…",
  "pollAfterMs": 3000
}

Poll a request

GET/api/v1/jobs/{requestId}

Polling is free and does not consume quota. A job is readable only with the key that started it. Poll about every three seconds; a typical read finishes in one to four minutes depending on how many objects are in the image.

200 · still working
{
  "requestId": "6f1c9b2e-…",
  "status": "running",
  "progress": {
    "stage": "extracting",
    "title": "Separating the first piece",
    "summary": "Found 3 clearly visible furniture items.",
    "items": [
      { "id": "piece-1", "simple_name": "sofa", "status": "complete" },
      { "id": "piece-2", "simple_name": "floor lamp", "status": "extracting" }
    ]
  },
  "pollAfterMs": 3000
}

When status becomes complete the body carries the full result described in the item object. A failed status carries a plain-language error and nothing else.

Synchronous read

POST/api/v1/inspire

Same inputs, same result shape, one open connection. The response arrives only when the whole read is finished, which can take several minutes — most HTTP clients, load balancers, and serverless runtimes will give up first. Use /jobs unless you control the timeout.

request
curl -X POST https://inspired.jetskibay.com/api/v1/inspire \
  -H "x-api-key: $INSPIRED_KEY" \
  -F "image=@outfit.jpg" \
  --max-time 600

List domains

GET/api/v1/domains

Unauthenticated. Returns the catalogue the API currently supports, which is the honest source for a picker in your own UI.

200 ok
{
  "domains": [
    {
      "id": "furniture",
      "label": "Furniture & interiors",
      "description": "Sofas, chairs, tables, lighting, rugs, shelving, and decor.",
      "aliases": ["interior", "home"],
      "default": false
    }
  ]
}

Key status

GET/api/v1/keys/current

Reports what is left of the key on the request: allowance, requests used, expiry. Free.

DELETE/api/v1/keys/current

Revokes it immediately. Use this if a key ends up somewhere it should not be.

Connect an agent

The same three capabilities are available over MCP at https://inspired.jetskibay.com/mcp. It is the JSON-only Streamable HTTP transport, protocol revision 2026-07-28: one JSON-RPC message per POST, a JSON response, no SSE stream and no session to open or delete. GET and DELETE answer 405 and say why.

Connect with an OAuth-capable MCP client and sign in through Auth0. The requested audience is https://inspired.jetskibay.com/mcp and the scope is agents. Approved users receive ten reads per account. Missing or invalid bearer tokens receive a 401 challenge with protected-resource metadata. Listing tools and collecting earlier reads remain free after the allowance is spent. REST API keys do not authenticate MCP.

Configure it in a client that speaks streamable HTTP:

{
  "mcpServers": {
    "inspired": {
      "type": "http",
      "url": "https://inspired.jetskibay.com/mcp"
    }
  }
}

Or drive it with nothing but curl:

curl -sS https://inspired.jetskibay.com/mcp \
  -H "Authorization: Bearer $INSPIRED_AUTH0_TOKEN" \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools[].name'
MCP has its own Auth0 allowance. It uses the same durable job starter and domain validation as REST, then records the verified user's ownership on the job. API keys are for REST only.

The three tools

ToolArgumentsCosts a request
list_focus_domains none no
start_visual_search image (base64 or a data: URL) and imageMediaType, or pinterestUrl; optional domain and focus yes, at the start
get_visual_search requestId no

There is no argument that takes an image URL. The platform accepts bytes you send it or a Pinterest pin, and will not fetch an arbitrary address on your behalf — download the image first.

A finished read comes back twice: as structuredContent for your code, and as text for the model to read. Both are trimmed for a context window. The cutouts become HTTPS URLs instead of inline base64, candidates are capped at ten per object, and the mood board is described with the route that returns it rather than pasted in as a megabyte of base64 — GET /api/v1/jobs/{requestId} still carries the bytes.

An unknown argument, an unknown domain or a failed read comes back as a tool result with isError: true and a message written to be read by a model, not a JSON-RPC protocol error. Protocol errors are reserved for malformed calls.

The item object

A completed read returns a summary, a mood board assembled from the cut-outs, and one entry per object.

200 · complete
{
  "requestId": "6f1c9b2e-…",
  "status": "complete",
  "domain": "furniture",
  "focus": "",
  "summary": "Found 3 clearly visible furniture items.",
  "grid": {
    "imageDataUrl": "data:image/webp;base64,…",
    "mimeType": "image/webp",
    "cells": [{ "cell": 1, "itemId": "piece-1", "name": "sofa" }]
  },
  "items": [
    {
      "id": "piece-1",
      "simple_name": "sofa",
      "full_description": "Low three-seat sofa in sage bouclé with rounded arms",
      "gridCell": 1,
      "imageDataUrl": "data:image/webp;base64,…",
      "candidates": [
        {
          "title": "Bouclé three-seater",
          "link": "https://shop.example/sofa-boucle",
          "imageUrl": "https://images.example/sofa.jpg",
          "price": "€1,299"
        }
      ]
    }
  ],
  "metadata": { "source": "photo", "domain": "furniture", "visionModel": "…" }
}
FieldMeaning
simple_nameTwo or three words naming the object.
full_descriptionWhat the reader saw: colour, material, shape, distinctive details. This is also the text query behind the search.
imageDataUrlThe cut-out itself, as a WEBP data URL on a white background.
candidatesUp to ten retail listings, in the order the search engine ranked them.
grid.imageDataUrlAll cut-outs composed into one square board.
Candidates are visually similar, not identified. Nothing in the pipeline claims a listing is the exact product in the photo, and no stage re-checks a cut-out against the original image. Present them as "shop something like this", not as a match.

Errors

Every error is { "error": "…", "code": "…" }. The message is safe to show a person; the code is what to branch on.

StatusCodeMeans
400unknown_domainThe domain field is not one we support.
400—No image or Pinterest URL, an unsupported file type, or a file over 10 MB.
401missing_api_keyNo credential on the request.
401invalid_api_keyThe key is not one of ours.
401expired_api_keyPast its 24 hours. Mint another.
401revoked_api_keyRevoked, by you or by a re-mint.
404—No such request, or it belongs to a different key.
422—The image contains nothing shoppable in the chosen domain.
429quota_exhaustedThe key's included requests are used up.
429rate_limitedToo many key requests from one address or network.
502/503—A provider or binding failed. The request is refunded to your key.

A request that fails because of us is given back to your allowance. A request that fails because of the image — nothing shoppable in it, wrong file type — is not.

Limits & cost

Every request behind a key costs a vision call, one image edit per object, and up to two searches per object. That is why the allowance is small and why quota is charged when a read starts rather than when it finishes.

Data & retention

Publication

Inspired publishes itself to Jetskibay under the jetskibay-publish protocol. The platform manifest is at /.well-known/jetskibay.json: it describes this tool, both endpoints, and indexes seven resource publications — a field guide, an agent skill, an article, the CLI, the focus-domain dataset, the handbook, and the collection that ties them together. Each resource has its own manifest at /<slug>/.well-known/jetskibay.json, and its files live under an immutable /<slug>/releases/1.0.0/.

Everything downloadable is also packaged as one archive, the Inspired starter kit, with a lock file listing the SHA-256 of every file in it. The hashes in the manifests are computed from the published bytes at build time, so you can verify anything you download.

The live status feed both hosts serve is at /status.json. It probes this Worker's own databases and object storage when you ask for it; the upstream vision, image-editing and search providers are not probed, and it says so rather than implying a green light nobody checked.

Questions, or production volume: hello@welovelooks.com.