Endpoints reference
The base URL is https://styleref.io/api/v1.
Anonymous — GET /styles, GET /styles/{ref}, GET /styles/{ref}/style.md, POST /style-md/validate. Pass a bearer token to additionally resolve your own private styles.
Authenticated — POST /extractions, GET /extractions/{id}, POST /styles/{ref}/save, GET /me/styles, GET /me/saved-styles, GET /me/extractions, GET /me/extractions/{id}. See Overview & authentication.
The {ref} path parameter
Endpoints that take a style accept a {ref} in one of four forms: a share slug (1an6edgp-c42c0cba5315), a /share URL, or — with a bearer token, for your own styles — the style’s UUID id or its /styles/{id} builder URL. When the ref contains / or : (both URL forms do), URL-encode the whole value (encodeURIComponent) so it stays one path segment. See Core concepts.
Every ref is an opaque id or a link containing one — you can’t build one from a style’s name. Get slugs from the search endpoint below or a share URL, and your own ids from GET /me/styles, which returns a ready-to-use ref per style. Anything else returns 400 invalid_request with a message naming the accepted forms.
GET /styles
Search the public gallery.
| Query param | Type | Notes |
|---|---|---|
query | string | Aesthetic, mood, discipline, or keywords. |
category | string | Gallery category filter. |
sort | popular | recent | Defaults to recent. |
limit | integer 1–25 | Defaults to 8. |
offset | integer 0–200 | Rows to skip. Pass back nextOffset for the next page. |
curl "https://styleref.io/api/v1/styles?query=warm%20editorial&limit=5"Returns JSON. Feed a result’s slug to /styles/{ref}:
{
"count": 1,
"styles": [
{ "name": "80s-Inspired Poster", "slug": "1an6edgp-c42c0cba5315", "author": "@styleref",
"category": "UI/UX", "tags": ["glass","calm"],
"url": "https://styleref.io/share/1an6edgp-c42c0cba5315", "heroImage": null,
"palette": ["#f4e3c1", "#c2452d", "#1b1b1b"] }
],
"offset": 0,
"hasMore": true,
"nextOffset": 5,
"attribution": "Generated by StyleRef.io — https://styleref.io"
}palette carries up to six hex colors from the style, so a picker can paint swatches without fetching each style.
Page with offset: pass nextOffset back as ?offset=, and stop when hasMore is false. When nothing matches on the first page, the response includes availableCategories as a retry hint.
GET /styles/{ref}
Get a style compiled to a ready-to-use spec.
| Query param | Type | Notes |
|---|---|---|
format | default | flux | midjourney | diffusion | stylemd | json | Defaults to default. |
sections | string | Comma-separated section ids (for example colors,typography) to cut tokens. |
compact | 1 | true | 0 | false | Strip boilerplate to save tokens. |
raw | 1 | true | 0 | false | Omit the metadata header/attribution and return only the compiled spec. |
format=json returns application/json; every other format returns text/markdown.
Your own styles serve your last generation
A ref that resolves to your own style compiles from the last time you pressed Generate on styleref.io — not from your live, unsaved edits. Blocks you added since that generation are left out, exactly like the public share view. Gallery styles are unaffected: a published style is already a frozen snapshot.
So the rule is the same everywhere: using a gallery style is free, using your own style as last generated is free, and getting fresh output after editing means generating it once on the web.
A style you have never generated has no output to serve, so it answers 409:
{
"error": "styleref_not_generated",
"message": "This StyleRef hasn't been generated yet — open it on styleref.io and Generate it first."
}message is written for a person — render it as-is. This applies to every format, and to /styles/{ref}/style.md.
The image formats carry only visual instructions: a style that mixes visual and writing-voice items compiles its voice items (formality, emoji and hashtag usage, tone of copy) into default and stylemd only. flux, midjourney and diffusion leave them out — in an image prompt they are wasted tokens that dilute the visual weighting.
A style’s guardrails compile per format, in each model’s native negation idiom: default/stylemd keep the instruction-following “Avoid …” prose; diffusion renders a separate Exclude: terse, comma, terms. section ready for a negative-prompt field; midjourney renders a comma-separated --no list; flux carries no negative terms at all (FLUX has no negative channel, and naming an unwanted element tends to attract it) — its guardrails are expressed as positive alternatives instead.
curl "https://styleref.io/api/v1/styles/1an6edgp-c42c0cba5315?format=flux&compact=1&raw=1"The response carries X-StyleRef-Canonical-Url (the public page), X-Token-Count (approximate token size), and an ETag. Send the ETag back as If-None-Match to revalidate a cached copy — an unchanged style answers 304 Not Modified with no body, so polling for staleness costs almost nothing.
const res = await fetch(
"https://styleref.io/api/v1/styles/1an6edgp-c42c0cba5315?format=default"
);
const spec = await res.text();
// Prepend `spec` to your AI prompt as a hard constraint.Returns 403 if the style exists but is private or pending moderation, 404 if no public style matches, 409 if it is your own style and has never been generated.
format=json also carries the style’s images
The structured format adds referenceImages when the style has any — the images you uploaded as inspiration, the image a style was extracted from, and its public sample outputs, flattened into one list with an explicit kind:
{
"sections": { "...": {} },
"referenceImages": [
{ "url": "https://…/inspo.png", "kind": "inspiration", "thumbnailUrl": "https://…/inspo_thumb.png" },
{ "url": "https://…/source.png", "kind": "source", "thumbnailUrl": null }
]
}Inspiration images come first, so referenceImages[0] is the most reference-like image. Use it to feed an image-to-image pipeline instead of relying on prose alone.
GET /styles/{ref}/style.md
Get a complete STYLE.md body, ready to commit.
curl "https://styleref.io/api/v1/styles/1an6edgp-c42c0cba5315/style.md" > STYLE.mdWrite it to STYLE.md in a project root and reference it from AGENTS.md. See Add STYLE.md to a project.
POST /style-md/validate
Lint a STYLE.md document. A failing document still returns HTTP 200 — read the valid field.
Send the document as text/markdown, or as JSON with a text field:
curl -X POST "https://styleref.io/api/v1/style-md/validate" \
-H "Content-Type: text/markdown" \
--data-binary @STYLE.md{
"valid": true,
"errors": [],
"warnings": [],
"meta": { "name": "80s-Inspired Poster", "generator": "styleref.io", "sections": ["colors"] }
}See Validate & lint for the full rule list.
POST /extractions
Requires a bearer token with the extract scope. Consumes extraction credits.
Extract a style from an image you hold locally — no public URL needed. Asynchronous: this starts the run and returns an id to poll.
Send multipart/form-data:
| Part | Type | Notes |
|---|---|---|
image | file | Required. Max 8 MB. JPEG, PNG, GIF, or WebP — detected from file contents, not the declared Content-Type. |
name | string | Optional draft name; defaults to the AI-suggested title. |
text | string | Optional extra description fused into the extraction. |
curl -X POST "https://styleref.io/api/v1/extractions" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "X-StyleRef-Client: comfyui" \
-F "image=@reference.png" \
-F "name=Crimson Editorial"Returns 202:
{
"extractionId": "run_abc~sIgN3d",
"statusUrl": "https://styleref.io/api/v1/extractions/run_abc~sIgN3d?name=Crimson%20Editorial",
"status": "running",
"creditsRemaining": 41,
"image": { "width": 1024, "height": 768, "mediaType": "image/png" }
}The extractionId is signed and bound to your account — no one else can poll it.
The image is downscaled server-side to ~1024px before it reaches the model, so you don’t pay vision tokens for a full-resolution upload. Don’t pre-shrink to 1024 yourself: the same upload is the source for the stored inspiration image (up to 1600px). If you need to reduce size for transport, WebP at ~2048px longest edge is the sweet spot.
Errors: 400 unsupported/corrupt image, 401 missing scope, 402 out of credits, 413 over 8 MB, 429 rate limited.
GET /extractions/{id}
Requires a bearer token with the extract scope.
Poll an extraction. While running:
{ "extractionId": "run_abc~sIgN3d", "status": "running", "message": "Still running — poll again in ~10s." }| Query param | Type | Notes |
|---|---|---|
save_as_draft | true | 1 | Save the result into your library as a draft. |
name | string | Draft name; defaults to the AI-suggested title. |
Without save_as_draft the compiled spec comes back inline, so a stateless client never has to touch your library:
curl "https://styleref.io/api/v1/extractions/run_abc~sIgN3d" \
-H "Authorization: Bearer YOUR_TOKEN"{
"status": "complete",
"saved": false,
"name": "Crimson Editorial",
"blocks": [{ "section": "colors", "type": "color_palette", "value": "crimson #DC143C" }],
"spec": "…ready-to-use style spec…",
"styleMd": "---\nname: Crimson Editorial\n…",
"lockedBlockCount": 0
}A plan can cap how many style items an extraction returns, keeping the highest-ranked ones. No current plan sets that cap — free and Pro both return the full extraction — but the fields exist so a client never has to guess whether a short list was truncated. If a cap is ever applied, the response says so:
{
"lockedBlockCount": 12,
"note": "Free plan: the top 10 style items were returned and 12 more are locked — Pro unlocks the full extraction: https://styleref.io/pricing"
}lockedBlockCount is 0 and note is absent when nothing was withheld — which is the case on every plan today. Render note verbatim when it does appear; it is the same message the web app shows.
With save_as_draft=true it saves and returns a reference plus an edit link:
{
"status": "complete",
"saved": true,
"idempotentReplay": false,
"style": { "ref": "e7c1f2a9-2f65-4f0e-9a3d-2b1c4d5e6f70", "name": "Crimson Editorial", "blockCount": 7 },
"editUrl": "https://styleref.io/styles/abc-123"
}Saving is idempotent — polling twice with the flag returns the same style with idempotentReplay: true, never a duplicate. The source image is attached to the draft as an inspiration image, matching what the app does.
A failed status returns a generic message and refunds the credit automatically. A 404 means no extraction with that id belongs to you.
POST /styles/{ref}/save
Save (like) a public gallery style to your library, so it appears under My Styles → Saved. Requires a bearer token with the styles:write scope. Idempotent — saving an already-saved style succeeds.
curl -X POST -H "Authorization: Bearer $TOKEN" \
"https://styleref.io/api/v1/styles/1an6edgp-c42c0cba5315/save"{
"saved": true,
"alreadySaved": false,
"name": "80s-Inspired Poster",
"slug": "1an6edgp-c42c0cba5315",
"libraryUrl": "https://styleref.io/my-styles"
}Returns 401 without a valid token or scope, 404 if no public style matches.
GET /me/styles
Requires a bearer token with the styles:read scope.
List your own library, including private styles.
| Query param | Type | Notes |
|---|---|---|
query | string | Narrow to styles whose name contains this text, case-insensitive. |
limit | integer 1–50 | Defaults to 20. |
cursor | string | Opaque — pass back nextCursor from the previous page. |
Unlike the gallery search, query matches the style name only — a private
library has no published tags or blurb to rank against.
curl "https://styleref.io/api/v1/me/styles?query=editorial&limit=20" \
-H "Authorization: Bearer YOUR_TOKEN"{
"count": 2,
"styles": [
{ "name": "Crimson Editorial", "slug": null, "ref": "e7c1f2a9-2f65-4f0e-9a3d-2b1c4d5e6f70",
"visibility": "private", "updatedAt": "2026-07-21T10:00:00Z",
"lastGeneratedAt": "2026-07-21T10:04:00Z",
"url": "https://styleref.io/styles/abc-123" }
],
"nextCursor": null
}Feed a ref to /styles/{ref} to compile it. nextCursor is null on the last page.
lastGeneratedAt is null when you have never generated that style on
styleref.io. Fetching such a style returns 409 styleref_not_generated, so check
this field before presenting a style as ready to load — a picker can flag the row
up front instead of letting the load fail.
GET /me/saved-styles
Requires a bearer token with the styles:read scope.
The styles you saved from the gallery (the sibling of /me/styles, which lists the ones you authored). Same card shape as GET /styles — query, category, limit and offset all work the same way — so a picker can offer “Saved” as a third tab next to Recent and Popular.
curl "https://styleref.io/api/v1/me/saved-styles?limit=8" \
-H "Authorization: Bearer YOUR_TOKEN"GET /me/extractions
Requires a bearer token with the styles:read scope.
Your past extractions, newest first, metadata only. Re-reading an extraction you already ran is free — reach for this before spending credits to extract the same source twice.
| Query param | Type | Notes |
|---|---|---|
style | uuid | Limit to one style (its id, from /me/styles). |
limit | integer 1–50 | Defaults to 20. |
cursor | string | Opaque — pass back nextCursor from the previous page. |
curl "https://styleref.io/api/v1/me/extractions?style=e7c1f2a9-2f65-4f0e-9a3d-2b1c4d5e6f70" \
-H "Authorization: Bearer YOUR_TOKEN"{
"count": 2,
"entries": [
{ "id": "8f14…", "styleId": "e7c1…", "createdAt": "2026-07-21T10:00:00Z",
"suggestedTitle": "Crimson Editorial", "blockCount": 18, "appliedCount": 7 },
{ "id": "2b09…", "styleId": "e7c1…", "createdAt": "2026-07-14T09:12:00Z",
"suggestedTitle": "Crimson Editorial", "blockCount": 15, "appliedCount": 0, "locked": true }
],
"lockedCount": 1,
"fullAccess": false,
"nextCursor": null
}Plan scope matches the web app: on the free plan the newest extraction per style is open and older ones are listed with locked: true; browsing across all your styles (omitting style) is a Pro feature and returns 403 plan_limit_reached otherwise.
GET /me/extractions/{id}
Requires a bearer token with the styles:read scope.
One stored extraction with its full block set. No credits — this is a read of something you already own.
| Query param | Type | Notes |
|---|---|---|
format | blocks | default | stylemd | Defaults to blocks. Adds spec or styleMd compiled from the usable blocks. |
Blocks are re-capped against your current plan rather than the plan you were on when the extraction ran, so a plan change applies retroactively. No plan caps extracted items today, so in practice every block comes back; when a cap does apply, withheld blocks are returned marked locked: true (so you can show what an upgrade unlocks) and excluded from any compiled spec.
Returns 403 plan_limit_reached for an older entry on the free plan, 404 if no extraction with that id belongs to you.
Next steps
- OpenAPI specification — the canonical contract.
- Overview & authentication — auth and rate limits.
- CLI — these endpoints from the terminal.