1Setup
Start every integration on staging with a test key. Switch the base URL and key for production only once everything below works.
| Staging | Production | |
|---|---|---|
| Gateway base URL | https://api.ymeedev.us | https://api.ymee.us |
| API key | ymee_test_… | ymee_live_… |
| Suggested env vars | YMEE_API_URL and YMEE_API_KEY | same names, production values |
NEXT_PUBLIC_ variable.2Sign in & API keys
You create keys yourself in the YMEE API admin. Everyone signs in with their @ymee.us Google account: there are no passwords.
| Staging | Production | |
|---|---|---|
| YMEE OS (your apps) | https://os.ymeedev.us | https://os.ymee.us |
| YMEE API admin | https://api.ymeedev.us/admin | https://api.ymee.us/admin |
Create the keys for a client
In each admin, open Clients → the client → API keys. A key is shown only once, so copy it straight into that client's env.
| Admin | Name | Environment | Billable |
|---|---|---|---|
api.ymeedev.us/admin | <Client> – dev | test | off |
api.ymee.us/admin | <Client> – production | live | on |
Leave Product on “All products”: the model name decides which feature a call is billed to. If a key ever leaks, revoke it there and create a new one.
3Clients & features
What each client can call today. Send the model name instead of a provider model id: YMEE picks the real model and enforces its settings, so models can change without a release on your side. Routing is managed in the admin (Clients → the client → Model routing).
SEASE/docs/sease
| Feature | Model name to send | Path | Routes to today | Cost |
|---|---|---|---|---|
| ASK shown to the client as “Ask Sease” status key ask | ymee-ask | /v1/gemini | gemini-3.8-flashGoogle Gemini | Billed to the client |
| Translation status key translation | ymee-translationset automatically by /v1/translate | /v1/translate | gpt-5.4-miniOpenAI | Included (YMEE pays) |
| Try-On status key try-on | ymee-try-on | /v1/gemini | gemini-3.1-flash-imageGoogle Gemini | Billed to the client |
ask, model name ymee-ask. Never use the brand name in code.The response header x-ymee-model shows which model actually answered. A new client or feature appears here as soon as it's set up in the admin.
4Calling a feature
Use the provider's official SDK on the path from the table above. Tools, system instructions, contents and streaming work exactly as with the provider. The examples use the default model names; use the ones in the client's table.
Google Gemini (@google/genai)
import { GoogleGenAI } from "@google/genai";
const ai = new GoogleGenAI({
apiKey: process.env.YMEE_API_KEY,
httpOptions: {
baseUrl: `${process.env.YMEE_API_URL}/v1/gemini`,
headers: {
"x-ymee-session": cart.id, // see Session headers
...(customerId ? { "x-ymee-user": customerId } : {}),
},
},
});
// A chat feature: ASK (streaming: generateContentStream)
await ai.models.generateContent({
model: "ymee-ask",
contents,
config: { tools, systemInstruction },
});
// An image feature, e.g. Try-On
await ai.models.generateContent({
model: "ymee-try-on",
contents: [faceRefs, garment, prompt],
config: { responseModalities: ["IMAGE"] },
});Anthropic and OpenAI
import Anthropic from "@anthropic-ai/sdk";
const anthropic = new Anthropic({ apiKey: process.env.YMEE_API_KEY, baseURL: `${process.env.YMEE_API_URL}/v1/anthropic` });
import OpenAI from "openai";
const openai = new OpenAI({ apiKey: process.env.YMEE_API_KEY, baseURL: `${process.env.YMEE_API_URL}/v1/openai/v1` });A model name belongs to one provider: call it on that provider's path (calling a Gemini model name on /v1/anthropic is refused with a clear error).
| Provider | Forwarded endpoints (anything else returns 404) |
|---|---|
| Google Gemini | generateContent, streamGenerateContent, countTokens, embedContent, GET /v1beta/models |
| Anthropic | POST /v1/messages (stream or not), POST /v1/messages/count_tokens, GET /v1/models |
| OpenAI | POST /v1/chat/completions, POST /v1/responses, POST /v1/embeddings, GET /v1/models |
Raw REST works too, e.g. POST {YMEE_API_URL}/v1/gemini/v1beta/models/ymee-try-on:generateContent with the header x-goog-api-key: <YMEE key>.
5Hide paused featuresrequired
The client and YMEE can set monthly spend limits or pause a feature from the client's portal. When a feature is paused the gateway refuses its calls, so the site must hide that feature rather than show an error.
Check status server-side, cached for about a minute
const res = await fetch(`${process.env.YMEE_API_URL}/v1/status`, {
headers: { authorization: `Bearer ${process.env.YMEE_API_KEY}` },
next: { revalidate: 60 },
});
const { products } = await res.json();
// One entry per feature, keyed by its status key (see Clients & features)
const show = (key: string) => products[key]?.enabled !== false;
show("ask"); // ASK ("Ask Sease" for SEASE)Each entry looks like { "enabled": true, "reason": null, "spent_usd": 12.4, "limit_usd": 200, "remaining_usd": 187.6 }. The status call is free and isn't metered. If it fails (network error), keep the features visible: the gateway still enforces pauses on the actual call.
Handle a pause mid-session
A feature can pause between two status checks. Its calls then return HTTP 403 (or 402 if YMEE's own cap is reached) with:
{ "error": { "type": "product_paused", "reason": "product_limit_reached",
"product": "try-on", "source": "ymee-gateway" } }When error.type === "product_paused", treat the feature as unavailable: hide it, show a friendly message, and don't retry.
6Translationrequired where enabled
Catalogue translation has its own endpoint, so you don't need an OpenAI SDK or key: it uses the client's YMEE key and the client's translation model name is set for you.
POST {YMEE_API_URL}/v1/translate
Authorization: Bearer <YMEE key>
Content-Type: application/json
{ "texts": ["Giacca 3L Shell", "Pantaloni"], "source": "it", "target": "en",
"format": "text", "context": "Luxury ski apparel product copy",
"glossary": { "SEASE": "SEASE", "sci alpinismo": "ski touring" } }
→ { "items": [{ "id": "…", "key": null, "text": "3L Shell Jacket", "status": "pending", "version": 1, "published": false }, …],
"translations": ["3L Shell Jacket", "Trousers"], "published": [false, false], "model": "…", "usage": { … } }format: "html" keeps tags intact (use it for product descriptions). source is optional (detected when omitted). Translations come back in the same order as texts.
Review, keys and sync
Every translation is kept for the client's team to review in their YMEE portal (Translations), where they edit and publish it. Send your own keys so results can be matched to your rows, then sync what the team publishes. Send the same target code every time (always it, not sometimes it-IT): each code is its own list.
POST {YMEE_API_URL}/v1/translate
{ "target": "it", "source": "en",
"texts": [{ "key": "pdp.addAll", "text": "Add all to bag", "source_hash": "<your sha256>" },
{ "key": "pdp.count", "text": "{count, plural, one {# piece} other {# pieces}}" }] }
→ { "items": [
{ "id": "6f1c…", "key": "pdp.addAll", "text": "Aggiungi tutto alla borsa", "status": "pending", "version": 1, "published": false },
{ "id": "a03e…", "key": "pdp.count", "text": "{count, plural, one {# capo} other {# capi}}", "status": "published", "version": 2, "published": true } ],
"translations": [ … ], "published": [false, true], "model": "gpt-5.4-mini-…", "usage": { … } }- One version per source text. An item whose source text already has a version is answered from it without the model: the published text, else the pending machine text. When nothing needed the model,
modelis"stored"and usage is zero. - Changed source = new version. Older versions are marked
stale; a published one stays published until the new version is published. force: true(on the request or one item) translates again into a new pending version; the published one stays served meanwhile.- Style guide. The client's team can set house rules and a glossary per language in the portal; every new machine translation follows them (your request's
glossarywins on conflicts). Existing versions don't change. - Statuses:
pending,published,superseded(replaced by a newer version),archived(key removed). Publishing is blocked when placeholders ({name}, plural/select,#) or tags (<b>) don't match the original.
GET {YMEE_API_URL}/v1/translations?target=it&updated_since=2026-10-01T00:00:00Z&limit=500
→ { "items": [{ "id": "6f1c…", "key": "pdp.addAll", "target": "it", "format": "text",
"source_text": "Add all to bag", "source_hash": "<your sha256>", "text": "Aggiungi tutto al carrello",
"status": "published", "stale": false, "version": 1,
"updated_at": "2026-10-05T10:12:03.418211+00:00", "published_at": "2026-10-05T10:12:03.418211+00:00" }],
"next_cursor": "eyJ0Ijoi…" } // null on the last page; keep the same filters while paging
POST {YMEE_API_URL}/v1/translations/import // at most 500; no model; idempotent on key + target + source text
{ "items": [{ "key": "pdp.addAll", "target": "it", "source_text": "Add all to bag", "text": "Aggiungi tutto",
"status": "published", "source_hash": "<your sha256>" }] }
→ { "created": 1, "unchanged": 0, "failed": 0, "items": [{ "index": 0, "result": "created", "id": "…", "version": 1 }] }
POST {YMEE_API_URL}/v1/translations/archive // at most 500 keys; no target = every language
{ "keys": ["pdp.oldBanner"], "target": "it" }
→ { "archived": 1 }The sync returns every version whose status or text changed since updated_since, oldest first, including ones sent back to review. Store the last updated_at you processed and pass it next time (a minute of overlap is safe: items are idempotent by id). These three endpoints use the same key, aren't metered and ignore spend limits; /v1/translate still follows pause and limits.
For a catalogue job
- Translate once, store the result. Run it when a product is created or its copy changes (a Shopify product webhook or a back-office action) and save the output to Shopify Translations or the CMS. Never translate on page load.
- Batch. Up to 100 strings and 60,000 characters per request. Group short fields (titles, options, tags); send long HTML descriptions in smaller batches.
- One target language per request. Loop over the languages you need.
- Use
contextandglossaryfor consistent terms: the brand, product line names and technical terms that must not change. - Allow time. A large batch can take tens of seconds. Run it from a background job, not inside a shopper's request, and retry
429/5xxwith backoff.
7Session headersrequired
Send the shopper's session on every AI call (not needed for translation jobs). It lets YMEE count conversations and link AI use to orders. Nothing extra runs in the shopper's browser: no cookies, no pixels.
| Header | Value |
|---|---|
x-ymee-session | On Shopify storefronts, the cart ID, e.g. gid://shopify/Cart/…. Elsewhere, the site's own session or conversation ID. |
x-ymee-user | Optional. The platform's customer ID when logged in. Never an email or name. |
x-ymee-tags | Optional. A JSON object stored with the call, e.g. {"product":"shell-jacket"}. |
With an SDK, pass them in the client's default headers (httpOptions.headers for Gemini). With fetch, send them as normal headers.
8Cart attributerequired on Shopify
On Shopify storefronts, when a shopper uses an AI feature, mark the cart once per feature, server-side, with the Storefront API. The attribute travels with the cart to the order, so YMEE can report which orders involved which feature.
mutation {
cartAttributesUpdate(
cartId: $cartId,
attributes: [{ key: "ymee_ai", value: "ask,try-on" }]
) { cart { id } }
}The value lists the features used, by status key. Keep any existing cart attributes when you set it (merge, don't replace), and list each feature once. Nothing else is needed for orders: YMEE reads them through its own Shopify app.
9Errors & retries
Errors from the gateway itself carry "source": "ymee-gateway". Errors from the provider are passed through unchanged.
| Status | Meaning | What to do |
|---|---|---|
400 | Model name called on the wrong path, or a malformed request | Fix the request |
401 | Missing or wrong YMEE key | Check YMEE_API_KEY for this environment |
403 | Feature paused, client suspended or feature not enabled for this client | Hide the feature (Hide paused features) |
402 | YMEE spend cap reached | Hide the feature |
404 | Unknown model name, or an endpoint that isn't forwarded | Check the model name and the endpoint list |
429 / 5xx | Provider busy or failing | Retry with exponential backoff |
When Google or Anthropic report overload (503 / 529), the gateway already retries once on a fallback model before answering, so an overload error that reaches you means both were busy.
10Go-live checklist
For each client:
11Not in scope
- Billing, invoices and payment: YMEE handles these with each client directly.
- Client portals (e.g.
sease.ymee.us): run by YMEE. - Revenue reporting from orders: YMEE builds it from the session headers and cart attribute.
Questions or a stuck call: send James the time of the call and the x-ymee-model header (or the error body). Every call is logged on YMEE's side, so it can be traced.