# Glaz — guide for AI agents > Glaz is a website builder. A human creates an agent token in the panel > (Agents section) and hands it to you. With it you act AS that human: > same permissions, every action logged under your agent id. Full guide with worked examples: https://glaz.uy/welcome-agents Machine-readable limits and endpoints: https://glaz.uy/welcome-agents.json ## Read this first - ALWAYS send your own User-Agent header (e.g. "mati-sync-bot/1.0") — on EVERY request, including the ones that fetch the PUBLISHED site to verify your work: client domains go through the same edge, and the classic failure is a sync that respects this on all API calls and then dies with eighteen 403s in its little verification helper at the very end. Requests with a bare curl/urllib signature can be blocked with a Cloudflare 1010 page — an HTML 403 that is NOT an API answer. If a 403 is not JSON with a `code` field, it is the edge, not your token: do not re-login, fix your User-Agent. - You can hold several sessions at once: logging in again does NOT kill previous sessions. They all die when the agent token expires or is deleted. - Defensive habit: when listing views, also check gen_website_id on each row matches the site you asked for, and never delete anything you did not create in the same run without your human confirming. ## Login POST https://glaz.uy/agents/login with JSON {"token":"glz_agent_..."} → {"status":"OK","session":"", ...}. Send the jwt on every later request as header: Authorization: Bearer Then GET /users/me → your sites with your role in each, and the caps. Site-scoped endpoints are prefixed: /web-{siteId}/... ## Endpoints POST /agents/login body: { token } Trade the agent token for a session JWT. Send it back as "Authorization: Bearer ". GET /users/me Who am I: sites + role per site, caps, and the agent this session came from. POST /web-{siteId}/gen-views/create body: { gen_website_id, kind: "page"|"layout"|"partial", route?, name?, title?, layout_gen_view_id? } Pages need route; partials need name; layouts need neither. GET /web-{siteId}/gen-views/get?id={viewId}&source=1 The view + versions; source=1 adds draft_source {html, css, js}. POST /web-{siteId}/gen-views/update-files body: { gen_view_id, html?, css?, js? } Writes a new draft version. Omitted parts keep their previous content. POST /web-{siteId}/gen-views/set-meta body: { gen_view_id, title?, meta_description? } POST /web-{siteId}/gen-views/set-route body: { gen_view_id, route } POST /web-{siteId}/gen-views/set-layout body: { gen_view_id, layout_gen_view_id } 0 detaches the layout. POST /web-{siteId}/gen-views/preview body: { gen_view_id, version_id? } Returns a signed URL, valid 30 minutes, that renders the draft. POST /web-{siteId}/gen-views/publish body: { gen_view_id } Draft goes live; edge cache invalidates itself. POST /web-{siteId}/gen-views/rollback body: { gen_view_id, version_id } Points production back to an older version. Instant. GET /web-{siteId}/gen-views/search?limit=200 The site’s views: pages, layouts, partials. POST /web-{siteId}/gen-websites/upload-file body: multipart: file (repeatable), label? Up to MAX_FILES_PER_UPLOAD per call. files[] is FLAT: each entry carries ok, filename, url, sha256, bytes, kind at the top level. File type is sniffed from the BYTES, not the extension — old sites are full of .png files that are really JPEG; those come back 415 with the real type in the message. A rejected file does NOT fail the batch: the other entries stored fine, so always check files[].ok per entry. Same content twice returns the existing file (deduped:1); SVGs are sanitized on save, so their stored sha256 differs from your local file. POST /web-{siteId}/gen-views/upsert body: { route | name, kind?: "page"|"partial", title?, meta_description?, layout_gen_view_id?, html?, css?, js?, publish? } Create-or-update by route in ONE idempotent call — THE sync endpoint. Omitted parts keep their content; publish:1 goes live in the same call. Layouts are excluded: create them once, reference by id. Changing the layout of an EXISTING page is Owner-only. GET /web-{siteId}/gen-websites/manifest The whole site with content identity: every view with a sha256 per part, every file with its sha256. Compare against your local state and upload ONLY what changed. sha256 "" means uploaded before hashing existed — treat as unknown. GET /web-{siteId}/gen-websites/get Site detail: domains (is_main marks the live one), views, guides. POST /web-{siteId}/gen-views/delete body: { gen_view_id | route } Soft-delete a page, by id or directly by route (Owner role only). The route’s cached page is purged immediately, so a redirect for it applies on the very next request. restore undoes it; trash lists what is deleted. POST /web-{siteId}/gen-views/restore body: { gen_view_id } Bring a page back from the trash, unpublished (Owner only). GET /web-{siteId}/gen-views/trash The deleted pages of the site. POST /web-{siteId}/gen-websites/save-route body: { id?, pattern, target, code?, is_catch_all? } REDIRECTS — old URLs to new ones ("/site/rooms" → "/habitaciones"). Patterns can end in * to keep the tail. An exact pattern purges that route’s cached page immediately — the redirect applies on the next request. Owner only: a catch-all can divert all traffic. POST /web-{siteId}/gen-websites/delete-route body: { id } Remove a redirect rule (Owner only). GET /web-{siteId}/gen-websites/files?q=&limit= The site file library — what is already uploaded, so you do not re-send it. GET /web-{siteId}/gen-websites/view-data The full panel payload: site, domains, views, forms, redirects. Heavier than manifest; use when you need domains or forms. POST /web-{siteId}/gen-websites/refresh-cache Force every page of the site to re-render (bumps all cache tags). Platform-staff only today — you should not need it: publish, delete and redirect changes invalidate on their own. If something looks stale for more than ~2 minutes, that is a bug worth reporting, not a purge to script around. POST /web-{siteId}/gen-websites/seo-run body: { strategy?: "mobile"|"desktop" } Run a PageSpeed report against the live site after publishing. Takes 40-90s; results land in GET …/seo-data. ## The template dialect (no JavaScript evaluation — ever) - {{ x }} prints escaped; {{{ x }}} prints raw HTML. - {{#each models.key}} … {{/each}} — inside: {{ field }}, {{ this }}, {{@index}}. - {{#if path}} … {{else}} … {{/if}} — no expressions, no operators, no helpers. - {{> partial_name }} includes a PARTIAL view; the name lives in the partial’s route column. - A layout MUST print {{{ content }}} exactly once — that is where the page lands. - Available variables: page.title, page.meta_description, page.route, website.name, website.main_domain ## What the platform injects (do not duplicate) - CSS is injected as one