{"title":"Alibrio Weather — integration guide","language":"en","intended_audience":"Teams integrating Alibrio Weather into their own apps and backends (embeds, automation). Written for consumers of the hosted service—not for operators configuring the Alibrio server.","human_documentation_url_path":"/doc","markdown":"# Alibrio Weather — integration guide\n\nUse your deployment base URL instead of `{base}` (for example `https://weather.example.com`).\n\nHuman-readable HTML for the **same audience** (**may** list host-specific routes not duplicated here): **`GET {base}/doc`**.\n\n## API keys\n\n### How to get one\n\n| Path | Who |\n|------|-----|\n| Sign in → accept terms → **Settings** → create API key | Users when accounts are enabled on this deployment |\n| **`POST {base}/api/api-keys`** with JSON `{\"name\":\"…\"}` while signed in | Automation using the same session as the web app |\n| Credential from **support / onboarding** | Enterprise or managed-hosting customers |\n\nThe plaintext secret is shown **once** when the key is created (UI or `POST` response field `key`).\n\n### What keys are for\n\n- Call **`POST {base}/api/weather-graphics/signed-url`** with **`Authorization: Bearer <your-api-key>`** or an **authenticated browser session** to mint **short-lived** iframe-safe URLs.\n\nUser-issued keys usually include scope **`weather_graphics:sign`**.\n\n**Public embeds:** **`POST …/signed-url`**, then **`GET …/render?t=…`**. Do not rely on undocumented URL patterns.\n\n## Endpoints (integration-focused)\n\n| Method | Path | Purpose |\n|--------|------|---------|\n| POST | `/api/weather-graphics/signed-url` | Create a **temporary** embed URL (returns JSON). |\n| GET | `/api/weather-graphics/render?t=…` | Load the graphic using the token from `signed_url` (no Bearer). |\n| GET | `/api/weather/demo/{id}` | HTML fragment for demo cities only: `1` Prague, `2` New York, `3` Singapore (no key). |\n| GET | `/api/api-keys` | List your keys (metadata only; no plaintext secrets). |\n| POST | `/api/api-keys` | Create a key: JSON `{\"name\":\"…\"}`; response includes plaintext `key` once. |\n| DELETE | `/api/api-keys/{id}` | Revoke the key UUID `{id}` (authenticated). |\n| GET | `/doc` | Consumer HTML documentation (host-specific; may include routes not listed in this JSON). |\n| GET | `/api/docs/developer-integration` | This guide as JSON (`markdown` field). |\n\n## Fair-use limits\n\nFigures below are **typical defaults**. Your subscription, trial, or private deployment **may differ** — check your agreement or ask support. Enforcement runs inside each service instance; counters may reset after maintenance or scaling.\n\n### Account\n- **3** active API keys per signed-in account when the app stores keys for you.\n- **10** distinct embed configurations per account when minting with a session or personal API key (graphic kind, WMO code, night flag, theme, optional card label fields). Additional **new** combinations → **403** `too_many_embed_configs`. Re-minting an existing combination still succeeds.\n- Authenticating with a **deployment-wide server credential** may follow different embed-configuration rules — confirm with your provider.\n\n### Mint `POST /api/weather-graphics/signed-url` (≈ **1 minute** sliding window)\n- **10** mints / minute / signed-in user (session)\n- **10** mints / minute / personal API key (`key_id` bucket)\n- **10** mints / minute / **shared bucket** for callers sharing one deployment-wide server credential\n- **30** mints / minute / caller IP\n- **`ttl_seconds`** is commonly clamped between **300** s and **900** s — your deployment may use another range\n- Over limit → **429** JSON `{ \"error\": \"rate_limited\" }`\n\n**Recommendation:** cache each successful mint on **your backend** (keyed by embed identity) and serve the same `signed_url` until shortly before `expires_at` (small clock skew margin). Minting on every browser hit will quickly exceed **10** mints/minute per user or API key.\n\n### Render `GET /api/weather-graphics/render?t=…`\n- Bad or expired token → **401**\n- **10** requests / minute / IP; **20** / minute / token (`t=` payload)\n- Concurrent HTML generation: max **20** globally and **5** per IP → **503** when saturated\n- Over rate limit → **429** (empty body on this route)\n- Successful HTML responses include **`Cache-Control`** with **`max-age`** bounded by remaining token lifetime and server-side render cache TTL (default cache entry **600** s)\n\n### Demo `GET /api/weather/demo/{id}`\n- Sample forecast HTML cached per demo slot; default cache often about **900** s (**15** minutes), depending on hosting.\n\n## Typical flows\n\n**Public browser embed:** your **backend** calls **`POST /api/weather-graphics/signed-url`**, returns the JSON to the page, and the browser sets **`iframe.src`** to **`signed_url`**.\n\n**Throttle mint:** store the mint response server-side and reuse it until just before **`expires_at`**; do **not** call Alibrio mint on every page view unless you accept **`429`** from mint rate limits.\n\nIf **signing is not enabled** on the deployment you call, `POST …/signed-url` responds with service unavailable — contact whoever operates that environment.\n\n**Trial hosts** sometimes relax authentication on undocumented routes — production integrations should use documented signing flows and keys only.\n\n---\n\n## Important: signed URLs are temporary\n\nSigned URLs are intentionally **short-lived**. Do not store them in HTML, CMS fields, databases, or static pages as **permanent** embed URLs. Think **S3 pre-signed URL** / time-bound token — **not** a permanent widget link.\n\nGenerate a signed URL **shortly before** it is shown to an end user. If the page can stay open longer than the token lifetime, your integration must **refresh** the iframe by obtaining a **new** signed URL from your backend.\n\n**Your backend** should cache mint responses per embed and refresh only near **`expires_at`** — not on every request — to stay under mint rate limits while keeping URLs short-lived.\n\n**Mint response** looks like:\n\n```json\n{\n  \"signed_url\": \"https://your.host/api/weather-graphics/render?t=…\",\n  \"expires_at\": \"2026-05-09T12:34:56Z\",\n  \"ttl_seconds\": 900\n}\n```\n\n- **`expires_at`** — RFC 3339 / ISO-8601 UTC instant when the token is no longer valid.\n- **`ttl_seconds`** — lifetime after server clamping (commonly **900**, with **300**–**900** bounds unless your provider configures otherwise).\n\nFor **long-lived pages**, refresh the signed URL **before** `expires_at` (and again after errors loading the iframe).\n\n### Recommended pattern\n\n1. Browser calls **your** backend (e.g. `GET /weather-card-url`).\n2. Your backend authenticates to Alibrio Weather and calls **`POST /api/weather-graphics/signed-url`**.\n3. Backend returns **`signed_url`**, **`expires_at`**, **`ttl_seconds`** to the browser.\n4. Browser sets **`iframe.src`** to **`signed_url`**.\n5. Before expiry, or after a failed load, request a **new** signed URL and update the iframe.\n\nNever paste a signed URL into a static site as a long-term `src`.\n\n### Example: refresh helper (browser)\n\nYour `/my-backend/weather-card-url` should proxy to Alibrio’s mint endpoint and return at least `signed_url` and `expires_at`.\n\n```javascript\nasync function refreshWeatherIframe() {\n  const res = await fetch(\"/my-backend/weather-card-url\");\n  const { signed_url, expires_at } = await res.json();\n  document.getElementById(\"weather-frame\").src = signed_url;\n  // Optionally schedule refresh from expires_at before the token lapses.\n}\n```\n"}