Sleipner3D, homeV1 · Open beta: what’s in it

Developers · API reference

API reference

Every endpoint of the HTTP API with its parameters, an example request and response, and its errors. New to it? The quick start takes four requests. For AI agents, use the MCP server instead.

On this page

Basics

Overview

Base URL
https://3d.sleipner.ai
Versioning
One version, with no version in the path. New fields can appear in answers: ignore the ones you don’t know.
Requests
JSON, UTF-8, with Content-Type: application/json. Requests with images are multipart/form-data.
Responses
JSON, never cached (Cache-Control: no-store), except the download, which is a 303 redirect. Field names are as shown: snake_case in the answer to a make or change and in credits, camelCase on an object.
Ids
Objects and jobs have UUIDs. The ids in the examples here are made up.

Basics

Authentication

Send your API key as a bearer token on every request. Create keys in Settings under API keys (up to 10); a key starts with slp_ and is shown once, so keep it in an environment variable, never in code you share. A key acts as you: it spends your credits and sees your objects, but can’t buy credits, manage keys or change your account. Revoking a key works from the next request.

Every request
curl -H "Authorization: Bearer $SLEIPNER_API_KEY" https://3d.sleipner.ai/api/credits

POST /api/objects takes a key only. The other endpoints also accept the signed-in browser session, which is what the site itself uses. A missing, wrong or revoked key is a 401:

Response
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token"
{
  "error": "Invalid or revoked API key."
}

Basics

Safe retries

Give every make or change a request_id (1–64 characters, your choice): an idempotency key, per API key. Sending the same id again returns the first object with 200 and "already_submitted": true, and charges nothing, so a timeout or a dropped connection can always be retried. Without one, every request makes a new object.

Basics

Rate limits

  • Per key: 60 requests at once, then 1 a second.
  • Making (POST /api/objects): 5 at once, then one every 30 seconds, per account. A request the capabilities or picture check holds up (a 422 scope_*, too_many_checks, images_unchecked) doesn't count; every other POST does, even a refused one.
  • Up to 2 objects queued or being made at once, per account.
  • Per IP address, for requests that carry a key: 120 at once, then 2 a second.

Over a rate limit the answer is 429 with Retry-After in seconds; there are no other rate-limit headers. Back off and try again; with a request_id a retried make is never charged twice. Need more? Tell us.

Response
HTTP/1.1 429 Too Many Requests
Retry-After: 30
{
  "error": "Too many requests. Wait a minute and try again."
}

Basics

Credits and charging

The API spends the same credits as the site, at the same prices. Credits are taken when a make or change is accepted and come back in full if the object fails or you cancel it. A job keeps the price it was submitted at. Everything that isn’t a make or change is free.

CallCredits
Make, Quickwas 350, now 200
Make, Standardwas 1,000, now 600
Change, surface only50 in the end (the tier’s price may be held at the start; the rest comes back)
Change, shape or stylethe tier’s price
Everything elsefree

Your balance and the live prices: GET /api/credits. Buy credits on the pricing page; a key can’t.

Basics

Errors

An error is an HTTP status with a JSON body: error, a sentence you can show a person. A refusal to make or change something also has a code to branch on, and some add fields (a 402 adds balance and needed). Nothing is charged on an error.

Response
HTTP/1.1 402 Payment Required
{
  "error": "This needs 600 credits and you have 500.",
  "code": "insufficient_credits",
  "balance": 500,
  "needed": 600
}
StatuscodeWhen
400-The body or a field is wrong ("Bad request: …"), or the request is a JSON product (send photos as multipart).
400invalidMaking refused the input: a description outside 3-600 characters, a change outside 2-300, an object that isn't finished yet, an unknown tier.
401-No key, or a key that is wrong or revoked.
402insufficient_creditsNot enough credits. The body has your balance and what is needed.
404not_foundNo such object or library model (on make or change); other routes answer 404 with no code.
422scope_*Held by the capabilities check (scope_scene, scope_structure, scope_vegetation, scope_animation, scope_effect, scope_text, scope_precision, scope_prohibited). The body's scope has status (uncertain, not_yet or refused), reason, suggestions, roadmap, notify_url and proceed_with: the flag that makes the same request go ahead (accept_uncertain or make_anyway; none when refused). Nothing is charged.
422share_not_eligibleshare: true on something that can't be shared: made from photos or reference images (or a change of one), or a request made with make_anyway or accept_uncertain.
403share_pausedSharing is paused on your account for a while, after several shares that weren't accepted. Make it without share.
409share_unavailableCommunity sharing isn't open. Make it without share.
400images_uncheckedReference images or a product's photos: the picture check couldn't run. Try again (or leave reference images out). Nothing is charged.
422scope_prohibitedReference images or a product's photos: a picture goes against the terms (someone else's character, brand or design; a real person; sexual, hateful or illegal content; a weapon to manufacture). A product's own brand and logo are fine. Can't be overridden; nothing is charged.
429-A rate limit. Retry-After says how many seconds to wait.
429too_many_checksMany requests held by the capabilities check in a short time. Wait a few seconds.
429too_many_activeAlready 2 objects in the works on your account. Wait for one to finish (no Retry-After).
503daily_budgetMaking is paused for today because of high demand. Nothing is charged.
503disabledMaking is switched off for a while. Nothing is charged.

The capabilities check (422)

Every make and change is checked against what V1 makes before anything is charged. A held request is a 422 whose scope says why, what to make instead, and which flag makes the same request go ahead. The whole list is GET /api/capabilities.

Response
HTTP/1.1 422 Unprocessable Entity
{
  "error": "We can't guarantee results for a castle yet: …",
  "code": "scope_structure",
  "scope": {
    "status": "uncertain",             // supported | uncertain | not_yet | refused
    "code": "scope_structure", "category": "structure", "kind": "large_building",
    "capability": "large_building",
    "message": "We can't guarantee results for a castle yet: …",
    "reason": "We can't guarantee results for a castle yet: …",
    "pitfalls": "Towers, windows and small details melt together, …",
    "suggestions": ["a small wooden cabin", "a stone well", "a wooden front door"],
    "suggestion": "a small wooden cabin",
    "roadmap": "Later: buildings and streets",
    "interest": "large-buildings",
    "notify_url": "https://3d.sleipner.ai/updates?interest=large-buildings",
    "can_override": false,             // uncertain: use proceed_with, not make_anyway
    "proceed_with": "accept_uncertain",
    "credits": "…"
  },
  "hint": "Results for this are not guaranteed. If the user accepts that, send the same request with accept_uncertain: true."
}
  • scope.status: supported (made; a note may come with it as scope_note), uncertain (results vary), not_yet (V1 can't do it yet) or refused (against the terms).
  • scope.proceed_with names the one flag that makes the same request go ahead: "accept_uncertain" for uncertain, "make_anyway" for not_yet, null for refused (nothing makes it go ahead).
  • scope.can_override is true only for not_yet (make_anyway). On uncertain it is false: that does not mean the request can't go ahead, it means use proceed_with (accept_uncertain: true), not make_anyway.
  • scope.reason (also message), pitfalls (uncertain: what tends to go wrong), suggestions and suggestion (single objects V1 makes instead), roadmap, interest and notify_url (a page the user opens to be told when it's ready; an agent can't opt the user in), credits (nothing was charged).

Endpoints

Objects

Make an object

POST/api/objectsAPI key

Describe one object; it is made with your credits. JSON in, a 201 with the new object's id out.

Parameters

  • kind"generate"bodyRequired

    Selects the action: "generate".

  • promptstringbodyRequired

    One object, 3 to 600 characters.

  • tier"quick" | "standard"body

    Default quick.

  • request_idstring, 1–64body

    Recommended. Your id for this request: the same id again returns the first object and charges nothing.

  • accept_uncertainbooleanbody

    After a 422 whose scope.status is uncertain (proceed_with "accept_uncertain"), send the same request with true once the user accepts that results vary.

  • make_anywaybooleanbody

    After a 422 whose scope.status is not_yet (proceed_with "make_anyway"), send the same request with true to try it regardless.

  • sharebooleanbody

    Share it with the community at the shared price. We review it first; in the beginning, review can take 1–5 days before a shared model is published. Not with reference images or photos, nor with make_anyway or accept_uncertain. Default false.

Prices: Quick 200 credits, Standard 600. They are taken when the request is accepted and come back in full if the object fails or you cancel it.

Every request first gets the capabilities check: something V1 can't make, or can't promise, is a 422 before anything is charged (see Errors). When it goes through with a caution, the answer adds scope_note and scope.

With share: true the price is the shared one and the answer adds shared: true and a share_note; follow the review in share on the object.

Example request

curl
curl -X POST https://3d.sleipner.ai/api/objects \
  -H "Authorization: Bearer $SLEIPNER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind":"generate","prompt":"A brass ship lantern with a glass chimney",
       "tier":"quick","request_id":"lantern-1"}'

Example response

201 Created
HTTP/1.1 201 Created
{
  "object_id": "6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
  "status": "queued",
  "page_url": "https://3d.sleipner.ai/objects/6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
  "api_url": "https://3d.sleipner.ai/api/objects/6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
  "price": 200,  // credits charged
  "list_price": 350,  // the list price (above price during the launch)
  "balance": 400,  // your balance afterwards
  "already_submitted": false
}
200 OK · same request_id again
HTTP/1.1 200 OK
{
  "object_id": "6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
  "status": "queued",
  "page_url": "https://3d.sleipner.ai/objects/6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
  "api_url": "https://3d.sleipner.ai/api/objects/6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
  "price": 200,
  "list_price": 350,
  "balance": 400,
  "already_submitted": true  // the first object; nothing charged
}

Errors

StatuscodeWhen
400-The body isn't valid: "Bad request: prompt …". The message names the field.
400invalidA description outside 3–600 characters, or an unknown tier.
402insufficient_creditsNot enough credits; the body has balance and needed.
422scope_*Held by the capabilities check; nothing charged. Retry with the flag in scope.proceed_with.
429too_many_active2 objects already in the works.

Change an object

POST/api/objectsAPI key

One change to one of your ready objects or a library model. You get a new object; the source stays as it is.

Parameters

  • kind"edit"bodyRequired

    Selects the action: "edit".

  • instructionstringbodyRequired

    The change, 2 to 300 characters: "make it dark green".

  • object_idstringbody

    One of your ready objects. Give this or library_slug.

  • library_slugstringbody

    A library model, e.g. "farm-wagon" (from /library.json).

  • tier"quick" | "standard"body

    Default quick. Sets the price if the shape has to change.

  • request_idstring, 1–64body

    Recommended. Your id for this request: the same id again returns the first object and charges nothing.

  • accept_uncertainbooleanbody

    After a 422 whose scope.status is uncertain (proceed_with "accept_uncertain"), send the same request with true once the user accepts that results vary.

  • make_anywaybooleanbody

    After a 422 whose scope.status is not_yet (proceed_with "make_anyway"), send the same request with true to try it regardless.

  • sharebooleanbody

    Share it with the community at the shared price. We review it first; in the beginning, review can take 1–5 days before a shared model is published. Not with reference images or photos, nor with make_anyway or accept_uncertain. Default false. Not for a change of an object made from photos or reference images.

A new colour, material or wear keeps the mesh and costs 50 credits. When that can't be told in advance, the tier's price is taken and the rest comes back when it finishes: the object then shows job.price (taken) and job.creditBack (returned).

Library slugs are in /library.json.

Example request

curl
curl -X POST https://3d.sleipner.ai/api/objects \
  -H "Authorization: Bearer $SLEIPNER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind":"edit","library_slug":"farm-wagon","instruction":"paint it dark green",
       "tier":"quick","request_id":"wagon-green"}'

Example response

201 Created
HTTP/1.1 201 Created
{
  "object_id": "7a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "status": "queued",
  "page_url": "https://3d.sleipner.ai/objects/7a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "api_url": "https://3d.sleipner.ai/api/objects/7a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "price": 200,  // the tier's price; the surface-only rest comes back
  "list_price": 350,
  "balance": 200,
  "already_submitted": false
}

Errors

StatuscodeWhen
400-Neither or both of object_id and library_slug: "Give either object_id or library_slug."
400invalidAn instruction outside 2–300 characters, or a source that isn't ready yet.
404not_foundNo such object on your account, or no such library model.
402insufficient_creditsNot enough credits.
422scope_*Held by the capabilities check; nothing charged.

Make or change with reference images

POST/api/objectsAPI key

The same request as multipart/form-data, with pictures that guide the style, materials and colours: 1–4 on a new object, 1 on a change.

Parameters

  • requestJSONmultipartRequired

    The same JSON as Make an object or Change an object, as a part with type=application/json.

  • imagesfilemultipartRequired

    JPEG, PNG or WebP; 256 to 8,192 px a side, at most 40 megapixels, up to 20 MB each and 40 MB together. Repeat the part for each picture.

The prompt decides what the object is; the pictures guide how it looks. Each picture is checked before anything is charged. share: true with images is refused (share_not_eligible).

Example request

curl
curl -X POST https://3d.sleipner.ai/api/objects \
  -H "Authorization: Bearer $SLEIPNER_API_KEY" \
  -F 'request={"kind":"generate","prompt":"a mid-century armchair in this mustard fabric",
     "tier":"quick","request_id":"chair-1"};type=application/json' \
  -F [email protected]

Example response

201 Created
HTTP/1.1 201 Created
{
  "object_id": "6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
  "status": "queued",
  "page_url": "https://3d.sleipner.ai/objects/6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
  "api_url": "https://3d.sleipner.ai/api/objects/6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
  "price": 200,
  "list_price": 350,
  "balance": 400,
  "already_submitted": false
}

Errors

StatuscodeWhen
400-No images part, or reference images are switched off.
400images_uncheckedThe picture check couldn't run. Try again, or leave the images out.
422scope_prohibitedA picture goes against the terms. Can't be overridden.
422share_not_eligibleshare: true with images.

List your objects

GET/api/objectsAPI key or session

Your objects, newest first, each in the same shape as Get an object.

Parameters

  • status"queued" | "running" | "ready" | "failed"query

    Only objects with this status. Any other value is ignored.

  • limitinteger, 1–100query

    Default 20.

Example request

curl
curl -H "Authorization: Bearer $SLEIPNER_API_KEY" \
  "https://3d.sleipner.ai/api/objects?status=ready&limit=20"

Example response

200 OK
HTTP/1.1 200 OK
{
  "objects": [
    { "id": "6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f", "title": "Brass ship lantern", "status": "ready", … }
  ]
}

Errors

StatuscodeWhen
401-No key or session.

Get an object

GET/api/objects/{id}API key or session

Status and progress while it is made; renders, size and files once it is ready. Poll every 10 to 15 seconds.

Parameters

  • idstringpathRequired

    The object_id from the make or change.

status is queued, running, ready or failed. A canceled object is failed with job.status canceled. A Quick object usually takes under 10 minutes, Standard 10–20.

job.status is queued, running, succeeded, failed or canceled; job.fraction (0 to 1) and job.stepLabel say how far it is; job.error says why it failed.

When ready, files has the poster, a light web GLB, the concept and renders (links valid one hour), size in metres, and variants: one per format and detail level. A variant's download is the website's link, which needs a browser sign-in; from code, use Download a file.

A shared object has job.shared true and a share with its review status (pending, needs_human, published, rejected, hidden or withdrawn), a label and detail, the reason when it wasn't accepted, and once published its public_path.

Example request

curl
curl -H "Authorization: Bearer $SLEIPNER_API_KEY" \
  https://3d.sleipner.ai/api/objects/6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f

Example response

200 OK · running
HTTP/1.1 200 OK
{
  "id": "6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
  "title": "Brass ship lantern",
  "prompt": "A brass ship lantern with a glass chimney",
  "kind": "generate",
  "status": "running",
  "url": "/objects/6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f",
  "createdAt": "2026-11-02T09:14:03.512Z",
  "parentId": null,
  "librarySlug": null,
  "basedOn": null,
  "job": {
    "id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f",
    "status": "running",
    "tier": "quick",
    "price": 200,
    "listPrice": 350,
    "creditBack": 0,
    "surfaceHold": false,
    "fraction": 0.45,  // 0..1
    "step": "shape",
    "stepLabel": "Building the 3D shape",
    "message": null,
    "error": null,
    "cancelRequested": false,
    "photos": null,
    "references": 0,
    "hasUserImages": false,
    "shared": false,
    "steps": [
      { "name": "size", "label": "Measuring", "status": "succeeded" },
      { "name": "concept", "label": "Drawing the concept", "status": "succeeded" },
      { "name": "shape", "label": "Building the 3D shape", "status": "running" }
    ]
  },
  "files": null,  // null until ready
  "share": null
}
200 OK · ready (files only)
HTTP/1.1 200 OK
{
  …
  "status": "ready",
  "files": {
    "poster": "<presigned URL, valid 1 hour>",
    "web": "<presigned URL, valid 1 hour>",
    "concept": "<presigned URL, valid 1 hour>",
    "renders": [
      { "view": "front34", "url": "<presigned URL, valid 1 hour>" },
      { "view": "back34", "url": "<presigned URL, valid 1 hour>" }
    ],
    "size": { "width": 0.18, "height": 0.42, "depth": 0.18 },  // metres
    "model": "…",  // what built the shape; "keep-mesh" after a surface-only change
    "variants": [
      {
        "format": "glb",
        "detail": "standard",
        "bytes": 6402118,
        "triangles": 48210,
        "label": "GLB",
        "download": "/objects/6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f/download?format=glb&detail=standard"
      },
      {
        "format": "glb",
        "detail": "low",
        "bytes": 1288540,
        "triangles": 9840,
        "label": "GLB",
        "download": "/objects/6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f/download?format=glb&detail=low"
      },
      {
        "format": "fbx",
        "detail": "standard",
        "bytes": 7950332,
        "triangles": 48210,
        "label": "FBX",
        "download": "/objects/6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f/download?format=fbx&detail=standard"
      }
    ]
  }
}

Errors

StatuscodeWhen
404-No such object on your account: {"error": "Not found"}.

Download a file

GET/api/objects/{id}/downloadAPI key or session

A 303 redirect to the file, on a link that works for 5 minutes. Let your client follow it. Downloads are free.

Parameters

  • idstringpathRequired

    A ready object.

  • format"glb" | "gltf" | "fbx" | "usdz" | "blend" | "obj"query

    Default glb. gltf and obj come zipped.

  • detail"standard" | "low"query

    Default standard.

Most HTTP clients drop the Authorization header when a redirect goes to another host, so the key never reaches the file store.

Example request

curl
curl -L -o lantern.glb -H "Authorization: Bearer $SLEIPNER_API_KEY" \
  "https://3d.sleipner.ai/api/objects/6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f/download?format=glb&detail=standard"

Example response

303 See Other
HTTP/1.1 303 See Other
Location: <presigned URL, valid 5 minutes>
Cache-Control: no-store

Errors

StatuscodeWhen
404-The object isn't ready, isn't yours, or has no such file.
503-Downloads are unavailable for a moment.

Cancel an object

POST/api/objects/{id}/cancelAPI key or session

Stops a queued or running object. Its credits come back.

Parameters

  • idstringpathRequired

    The object to stop.

A queued object is canceled and refunded at once: {"status": "canceled"}. A running one answers {"status": "running"}: it stops within a minute and is refunded then. One that already finished answers with its final job status.

Example request

curl
curl -X POST -H "Authorization: Bearer $SLEIPNER_API_KEY" \
  https://3d.sleipner.ai/api/objects/6f1c2d3e-4a5b-4c6d-8e7f-0a1b2c3d4e5f/cancel

Example response

200 OK
HTTP/1.1 200 OK
{ "status": "canceled" }

Errors

StatuscodeWhen
404-No such object on your account.

Endpoints

Credits

Balance and prices

GET/api/creditsAPI key or session

Your balance, the price of each tier now, and your latest 20 credit entries.

Parameters

None.

credits is what is charged now; list_price is the list price, higher than credits during the launch (until launch_price_until, 2026-12-31). A job keeps the price it was submitted at.

Entry kinds: grant (free credits), purchase, spend, topup, refund, adjust, reversal. A key can read the balance but never buy credits.

Example request

curl
curl -H "Authorization: Bearer $SLEIPNER_API_KEY" https://3d.sleipner.ai/api/credits

Example response

200 OK
HTTP/1.1 200 OK
{
  "balance": 400,
  "prices": [
    { "tier": "quick", "label": "Quick", "credits": 200, "list_price": 350, "launch_price_until": "2026-12-31" },
    { "tier": "standard", "label": "Standard", "credits": 600, "list_price": 1000, "launch_price_until": "2026-12-31" },
    { "tier": "accurate", "label": "Accurate", "credits": 5000, "list_price": 5000 }
  ],
  "entries": [
    {
      "kind": "spend",
      "amount": -200,
      "note": "New object (Quick)",
      "object_job_id": "c4d5e6f7-a8b9-4c0d-9e1f-2a3b4c5d6e7f",
      "created_at": "2026-11-02T09:14:03.498Z"
    },
    {
      "kind": "grant",
      "amount": 600,
      "note": "Free credits",
      "object_job_id": null,
      "created_at": "2026-11-01T17:02:41.120Z"
    }
  ]
}

Errors

StatuscodeWhen
401-No key or session.

Redeem a credit code

POST/api/credits/redeemAPI key or session

Adds a code's free credits to your account. Each code works once per account.

Parameters

  • codestringbodyRequired

    The code, for example from a game jam.

JSON only: Content-Type must be application/json.

Example request

curl
curl -X POST https://3d.sleipner.ai/api/credits/redeem \
  -H "Authorization: Bearer $SLEIPNER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"code":"JAMEXAMPLE26"}'

Example response

200 OK
HTTP/1.1 200 OK
{
  "credits": 600,
  "balance": 1000,
  "message": "600 credits added. They never expire."
}

Errors

StatuscodeWhen
400-No code, or a code that isn't valid or has run out (one message for both).
409-You've already used this code.
415-The body isn't JSON.
429-Too many tries; Retry-After says how long to wait.

Endpoints

Public

Capabilities

GET/api/capabilitiesNo auth

What V1 makes, may get wrong (uncertain) and can't do yet (not_yet), with examples, pitfalls and roadmaps. The same list the capabilities check uses.

Parameters

None.

Public and cached for an hour. MCP clients get the same from the list_capabilities tool.

Example request

curl
curl https://3d.sleipner.ai/api/capabilities

Example response

200 OK (shortened)
HTTP/1.1 200 OK
{
  "version": "…",
  "statuses": { "supported": "We make it. Just create it.", "uncertain": "…", "not_yet": "…" },
  "credits": "…",
  "categories": [
    {
      "id": "…", "title": "…",
      "capabilities": [
        { "id": "…", "status": "supported", "title": "…", "explanation": "…", "examples": ["…"], "roadmap": null, "interest": null }
      ]
    }
  ],
  "interests": [{ "id": "…", "title": "…" }]
}

Library catalog

GET/library.jsonNo auth

Every free library model with its size, formats, detail levels and renders, plus the packs. Slugs here work as library_slug.

Parameters

None.

Downloading a library model needs a browser sign-in on the website, or the MCP tool get_download_link.

Example request

curl
curl https://3d.sleipner.ai/library.json

Example response

200 OK (shortened)
HTTP/1.1 200 OK
{
  "name": "…", "url": "https://3d.sleipner.ai/library", "mcp": "https://3d.sleipner.ai/mcp",
  "licence": { "name": "…", "url": "…", "status": "…", "note": "…" },
  "packs": [{ "slug": "…", "title": "…", "description": "…", "url": "…", "models": ["…"] }],
  "models": [
    {
      "slug": "farm-wagon", "title": "…", "category": "…", "tags": ["…"],
      "size_m": { "width": …, "height": …, "depth": … },
      "formats": ["glb", "gltf", "fbx", "usdz", "blend", "obj"],
      "variants": [{ "format": "glb", "detail": "standard", "bytes": …, "triangles": … }, …],
      …
    }
  ]
}

Health

GET/api/healthNo auth

Whether the app and its database are up.

Parameters

None.

Example request

curl
curl https://3d.sleipner.ai/api/health

Example response

200 OK
HTTP/1.1 200 OK
{ "ok": true, "db": true }

Examples

Make, follow, download

The whole flow in three languages: make an object, poll it, save the GLB.

curl
export SLEIPNER_API_KEY=slp_...

# 1. Make an object
curl -s -X POST https://3d.sleipner.ai/api/objects \
  -H "Authorization: Bearer $SLEIPNER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"kind":"generate","prompt":"A brass ship lantern with a glass chimney","tier":"quick","request_id":"lantern-1"}'

# 2. Follow it (every 10-15 s) until "status" is "ready" or "failed"
curl -s -H "Authorization: Bearer $SLEIPNER_API_KEY" https://3d.sleipner.ai/api/objects/<object_id>

# 3. Download (-L follows the 303 to the file)
curl -L -o lantern.glb -H "Authorization: Bearer $SLEIPNER_API_KEY" \
  "https://3d.sleipner.ai/api/objects/<object_id>/download?format=glb&detail=standard"
JavaScript
// Node 18+ (fetch is built in). Save as make.mjs, run: node make.mjs
import { writeFile } from "node:fs/promises";

const API = "https://3d.sleipner.ai";
const auth = { Authorization: `Bearer ${process.env.SLEIPNER_API_KEY}` };

const res = await fetch(`${API}/api/objects`, {
  method: "POST",
  headers: { ...auth, "Content-Type": "application/json" },
  body: JSON.stringify({
    kind: "generate",
    prompt: "A brass ship lantern with a glass chimney",
    tier: "quick",
    request_id: "lantern-1", // a retry with the same id returns the same object, charged once
  }),
});
const made = await res.json();
if (!res.ok) throw new Error(`${res.status} ${made.code ?? ""}: ${made.error}`);

let object;
do {
  await new Promise((resolve) => setTimeout(resolve, 15_000));
  object = await (await fetch(made.api_url, { headers: auth })).json();
  console.log(object.status, object.job?.stepLabel ?? "", Math.round((object.job?.fraction ?? 0) * 100) + "%");
} while (object.status === "queued" || object.status === "running");
if (object.status !== "ready") throw new Error(object.job?.error ?? "The object failed; its credits were refunded.");

// fetch follows the 303 to the file and drops the key on the way (another host).
const file = await fetch(`${API}/api/objects/${object.id}/download?format=glb&detail=standard`, { headers: auth });
if (!file.ok) throw new Error(`download: ${file.status}`);
await writeFile("lantern.glb", Buffer.from(await file.arrayBuffer()));
Python
# Python 3 with requests (pip install requests)
import os, time, requests

API = "https://3d.sleipner.ai"
s = requests.Session()
s.headers["Authorization"] = f"Bearer {os.environ['SLEIPNER_API_KEY']}"

r = s.post(f"{API}/api/objects", json={
    "kind": "generate",
    "prompt": "A brass ship lantern with a glass chimney",
    "tier": "quick",
    "request_id": "lantern-1",  # a retry with the same id returns the same object, charged once
})
made = r.json()
if not r.ok:
    raise SystemExit(f"{r.status_code} {made.get('code', '')}: {made['error']}")

while True:
    time.sleep(15)
    obj = s.get(made["api_url"]).json()
    job = obj["job"] or {}
    print(obj["status"], job.get("stepLabel"), f"{round(job.get('fraction', 0) * 100)}%")
    if obj["status"] not in ("queued", "running"):
        break
if obj["status"] != "ready":
    raise SystemExit(job.get("error") or "The object failed; its credits were refunded.")

# requests follows the 303 and drops the key when the host changes.
f = s.get(f"{API}/api/objects/{obj['id']}/download", params={"format": "glb", "detail": "standard"})
f.raise_for_status()
with open("lantern.glb", "wb") as out:
    out.write(f.content)