Aveiro API
Authentication
Sites
List Sites
List Templates
Create Site
Pages
Create a Page
Delete a Page
List Pages
Reorder Pages
Update a Page
Site Media
Publish Site
Audience
Newsletter API Overview
Newsletter Campaigns
Social
Social API Overview
Social Accounts
Social Media
Social Posts
Social Performance
Website Mockup Video
TrademarkTrademark
Ctrl k
Search…
Sign up
Aveiro API
Authentication
Sites
List Sites
List Templates
Create Site
Pages
Create a Page
Delete a Page
List Pages
Reorder Pages
Update a Page
Site Media
Publish Site
Audience
Newsletter API Overview
Newsletter Campaigns
Social
Social API Overview
Social Accounts
Social Media
Social Posts
Social Performance
Website Mockup Video

Site Media

List site images, upload files, and capture product screenshots from public URLs.
Updated 2d ago
Update a Page
Publish Site
Sponsored
Aveiro
aveiro.app
Sponsored
Aveiro
aveiro.app
TrademarkTrademark
AveiroBlogContact
© Aveiro. All rights reserved.
Built with Aveiro
In short
GET /api/v1/sites/{siteId}/media lists images. POST …/media/upload returns a signed URL for PUT uploads. GET …/media/screenshot-styles lists mockup looks; POST …/media/screenshot films a public page (async — poll GET …/screenshot/{renderId} and read warnings); POST …/media/screenshot-login returns a sign-in link for login-gated pages (free). Persist the canonical delivery path from upload (`publicUrl`) in MDX or metadata — not a signed URL. GET list responses also include optional `displayUrl` for immediate previews.

Why this exists

Pages should illustrate with real files, never invented image URLs (made-up links 404 and render broken). Agents check the library first, reuse what's there, and upload only when a new image is genuinely needed. Files land in the same site media library the dashboard's upload dialog uses, so everything stays visible in the editor.

List site media

GET /api/v1/sites/{siteId}/media?cursor=0&limit=24 Authorization: Bearer av_live_…
Required scope: content:read 200 OK url is the canonical unsigned reference — persist this in page content. displayUrl is signed for the current token holder and changes over time; use it only to preview or download right now, not to embed in MDX.

Prepare an upload

POST /api/v1/sites/{siteId}/media/upload Authorization: Bearer av_live_… Content-Type: application/json { "contentType": "image/png", "size": 482133 }
Required scope: content:write 201 Created Then PUT the raw bytes with exactly the returned headers:
curl -X PUT -H 'Content-Type: image/png' \ --data-binary @cover.png '…upload.url…'
Once the PUT succeeds, embed the returned publicUrl (canonical delivery path) in page MDX or metadata:
<Media src="/api/media/site-media/SITE_ID/1754900000000-ab12cd.png?k=s.224eeebe-974f-4cfa-96ce-75fb5166c7b0.voSKxMzNi7OqQDeTMfj2e4o_g2EEGvtk" alt="Launch cover" />
Aveiro signs that path when a page is rendered — visitors on a live site get a standing grant; members previewing a draft get a short-lived grant only when they have site access. The delivery route re-checks site liveness on every read (not whether the storage key belongs to that site), which is what lets gallery templates keep working: cloning copies content and media references, not the underlying files. Unpublishing revokes visitor access. Legacy absolute Supabase URLs stored before this change are still recognised on read.

Read media (delivery route)

Uploaded objects are not world-readable. Browsers and platforms fetch them through:
GET /api/media/<storage-key>[?k=<delivery-grant>][?t=<share-token>]
No bearer token — authorization is carried in the URL or, as a last resort, the caller's session cookie.
Grant
Who can read
None (avatars/ and organization-logos/)Anyone — identity assets by design
?k= visitor grantAnyone while the site named in the grant stays published — including media paths that still point at a gallery template source. Unpublishing revokes access.
?k= member grantDashboard or API caller who already had access when the URL was minted
?t= share tokenAny consumer with no session — social platforms at post publish, email recipients at send time
Session cookie (no grant)Signed-in org members when client-composed HTML references bare /api/media paths without a grant — for example campaign preview iframes
Page MDX, site settings, and folder listings store the unsigned path. Signing happens at read (published pages, dashboard editor/settings reads, dashboard-home recent-site covers, marketing showcase gallery covers, logos, collection listings, campaign draft HTML, social asset thumbnails, product storefront images in the editor and on live product pages / Products / Product Showcase blocks) or at outbound send (email HTML). Persist publicUrl / url from upload or list responses — not displayUrl. Email and social share grants: Social media uploads. Platform guide: Campaign images.

List screenshot styles

Before ordering a screenshot, list the camera and backdrop presets Frametic exposes. Pass a style's id back as styleId on the screenshot POST; without one, the render uses the project's default look.
GET /api/v1/sites/{siteId}/media/screenshot-styles?featured=1&limit=24 Authorization: Bearer av_live_…
Required scope: content:write 200 OK featured=1 narrows to the curated gallery (recommended for pickers). The response lists metadata only — the server resolves styleId when you queue the render. An unknown id returns 404 NOT_FOUND rather than silently falling back to the default look.

Capture a product screenshot

When a page should show the actual product — a docs page illustrating the screen it documents, or a blog post with a real pricing page — film a publicly reachable URL in a 3D device mockup and render one frame into the site's media library. This is asynchronous: a browser loads the page first, so the POST returns a pending render you poll until an image exists. Use upload when you already have a file. Use screenshot when the illustration should be a live page. Use AI image generation when the illustration is a concept, not a screen.
POST /api/v1/sites/{siteId}/media/screenshot Authorization: Bearer av_live_… Content-Type: application/json { "url": "https://www.example.com/pricing", "estimateOnly": true }
Required scope: content:write Pass estimateOnly: true to price the render without starting or charging anything. A still costs 10 AI credits (flat — cheaper than a mockup video). Credits are debited when the render is queued and refunded automatically if it fails. 200 OK (estimate) Start the render by omitting estimateOnly or setting it to false. Look options (all optional — compose over each other):
  • styleId — preset id from GET …/screenshot-styles
  • theme — capture variant to frame: "light" or "dark" (only when the page was captured in that theme)
  • framing — camera distance (0.4–3; below 1 is tighter)
  • chrome — "none", "light", or "dark" browser chrome around the page
{ "url": "https://www.example.com/pricing", "styleId": "preset_abc123", "theme": "dark", "framing": 1.1, "chrome": "light" }
202 Accepted Poll until the image exists:
GET /api/v1/sites/{siteId}/media/screenshot/{renderId} Authorization: Bearer av_live_…
Required scope: content:write 200 OK while rendering 200 OK when done Read warnings before embedding. A page that bounced to a login form renders perfectly — the image is real, but it is a picture of a login screen. warnings is separate from error: the render succeeded. Embed imageUrl in page MDX or metadata:
<Media src="https://…/site-media/SITE_ID/1754900000000-ab12cd.png" alt="Pricing page on desktop" />
Constraints
  • The URL must be publicly reachable — login-gated pages need a one-time sign-in via POST …/screenshot-login first
  • Pass recapture: true to re-film a page that changed since the last shot (costs an extra browser capture)
  • List styles with GET …/screenshot-styles when the shot should match a specific mockup look
  • theme selects which capture variant is framed — a site captured only in light cannot be filmed dark
  • Typical turnaround is about a minute; keep polling GET …/screenshot/{renderId} rather than inventing URLs
  • Always check warnings on the poll response before embedding
  • When warnings mentions a login redirect, call POST …/screenshot-login, have the owner sign in, then retry with recapture: true

Sign in for login-gated pages

When a page redirects to a sign-in form, the screenshot still renders — but warnings tells you the image is a login screen. Automation cannot type passwords; instead, mint a sign-in link the page owner opens once. Frametic stores an encrypted session against the site, so every later screenshot of that site inherits the sign-in.
POST /api/v1/sites/{siteId}/media/screenshot-login Authorization: Bearer av_live_… Content-Type: application/json { "url": "https://app.example.com/dashboard" }
Required scope: content:write 201 Created Give loginUrl to a person who can sign in. The link expires in about an hour. When interactive is false, the hosted browser is unavailable on this deployment and the page may ask for credentials by hand instead. No credits charged — nothing is rendered. After they confirm sign-in, retry POST …/media/screenshot with recapture: true on the same URL.

Limits

  • Image types: JPEG, PNG, WebP, GIF (no SVG — signed uploads bypass server-side sanitization)
  • Max size 10MB; the signed URL is valid for 2 hours
  • Uploads are capped per rolling day per site, and count against the organization's storage quota
  • Screenshot stills cost 10 AI credits each; debited when queued, refunded on failure

Errors

HTTP
Code
When
400VALIDATION_ERRORUnsupported type or size over the cap
403FORBIDDENMissing scope or storage quota exceeded
429RATE_LIMITEDDaily upload limit reached
402QUOTA_EXCEEDEDOrganization AI credits exhausted (screenshot queue)
501NOT_CONFIGUREDFrametic screenshots not configured on this server
502FRAMETIC_ERRORScreenshot render failed upstream
404NOT_FOUNDNo such screenshot render for this site, or unknown styleId

Related

  • Create a page
  • Update a page
  • Social media uploads
{
  "images": [
    {
      "name": "1754900000000-ab12cd.png",
      "url": "/api/media/site-media/SITE_ID/1754900000000-ab12cd.png?k=s.224eeebe-974f-4cfa-96ce-75fb5166c7b0.voSKxMzNi7OqQDeTMfj2e4o_g2EEGvtk",
      "displayUrl": "/api/media/site-media/SITE_ID/1754900000000-ab12cd.png?k=s.224eeebe-974f-4cfa-96ce-75fb5166c7b0.voSKxMzNi7OqQDeTMfj2e4o_g2EEGvtk?k=…",
      "createdAt": "2026-08-01T09:00:00.000Z",
      "size": 482133
    }
  ],
  "nextCursor": null
}
{
  "upload": {
    "url": "https://…signed-upload-url…",
    "method": "PUT",
    "headers": { "Content-Type": "image/png" },
    "expiresInSeconds": 7200
  },
  "publicUrl": "/api/media/site-media/SITE_ID/1754900000000-ab12cd.png?k=s.224eeebe-974f-4cfa-96ce-75fb5166c7b0.voSKxMzNi7OqQDeTMfj2e4o_g2EEGvtk",
  "path": "site-media/SITE_ID/1754900000000-ab12cd.png",
  "name": "1754900000000-ab12cd.png",
  "mediaType": "image",
  "maxBytes": 10485760
}
{
  "styles": [
    {
      "id": "preset_abc123",
      "name": "MacBook on desk",
      "description": "Laptop mockup with soft daylight",
      "featured": true,
      "author": "Frametic",
      "previewImageUrl": "https://…",
      "previewSourceHost": "example.com"
    }
  ]
}
{ "estimate": true, "credits": 10 }
{
  "estimate": false,
  "renderId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "pending",
  "sourceUrl": "https://www.example.com/pricing",
  "credits": 10
}
{
  "renderId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "pending",
  "sourceUrl": "https://www.example.com/pricing",
  "imageUrl": null,
  "credits": 10,
  "error": null
}
{
  "renderId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "done",
  "sourceUrl": "https://www.example.com/pricing",
  "imageUrl": "https://…/assets/site-media/SITE_ID/1754900000000-ab12cd.png",
  "width": 1920,
  "height": 1080,
  "credits": 10,
  "error": null,
  "warnings": ["The page redirected to a login form — the image shows a sign-in screen, not the requested URL."]
}
{
  "loginUrl": "https://…",
  "sourceUrl": "https://app.example.com/dashboard",
  "interactive": true,
  "expiresAt": "2026-09-08T08:30:00.000Z"
}