Getting Started
Search Printables in two steps, then follow a model through to its designer, its collections, and the contests it placed in.
1. Get your API key
Create an account and generate a FetchLayer API key from your dashboard. There is no Printables or Prusa account involved, and nothing to register.
Create free account →2. Search for models
curl -X POST https://api.fetchlayer.dev/printables/search-models \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"query":"cable management","sort":"popular","price":"free","limit":30}'Tip
/resolve-url first — it tells you whether the URL is a model, a designer, a collection, a contest or a search, and hands you the id the matching endpoint wants. It makes no upstream request and consumes no credit. 3. Learn the filter ids before you filter
Category, licence, material, printer and design-tool filters all take ids, never names — and there are 639 valid printer values alone. An id that does not exist returns an empty result set rather than an error, so a guessed printer name looks exactly like "nothing matched". Read the vocabulary once and cache it:
curl -X POST https://api.fetchlayer.dev/printables/filter-values \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{}'The response carries the category tree, the licences, the materials, every printer with its family, and the design tools — each with the id to pass back. Calling it does not consume a credit.
4. Follow a model outwards
Every model carries an id and a designer. Pass the id to /model-detail for the full record — description, licence, gallery, file listing with per-file slicing settings, remix sources and contest placings — or pass the designer handle to /user-models and /user-collections to see everything they publish and curate.
curl -X POST https://api.fetchlayer.dev/printables/model-detail \
-H "Authorization: Bearer ss-your-key" \
-H "Content-Type: application/json" \
-d '{"model":"https://www.printables.com/model/512345-110-scale-rc-shock-spring-organizer"}'Files are described, not downloadable
GET /media streams gallery images, file previews, avatars and the instructions PDF; it does not serve geometry or sliced files. 5. Read the notes
Every JSON response carries a notes[] array. It is where a truncated walk, a listing that could not be paged further, or an upstream limit is reported — the difference between "this designer published 143 models" and "we stopped at 143". Check it before treating any result set as complete.
{
"notes": [
"Listing truncated at 100 pages; pass the returned cursor to continue."
],
"modelCount": 2000,
"hasNextPage": true
}Error responses
Every error, on every route, is JSON in one shape: error (what went wrong, in words), status, code, retryable and requestId, which also comes as the X-Request-Id header. Branch on code and retryable rather than on the text; on a 5xx the text is generic. Quote requestId when reporting a problem.
{
"error": "Missing required parameter: url",
"status": 400,
"missing": ["url"],
"code": "invalid_input",
"retryable": false,
"requestId": "922874e3-c0d0-40d8-8866-d198af132420"
}| code | Status | Retryable | Meaning |
|---|---|---|---|
| invalid_input | 400 | no | The request needs fixing; error names the field. A missing required parameter also lists it in missing. |
| unauthorized | 401 | no | The API key is missing or not valid. |
| insufficient_credits | 402 | no | Nothing left to spend: no credits, a used-up free trial, or a monthly allowance and credit balance both spent. error says which. Waiting does not help; topping up does. |
| forbidden | 403 | no | This key or account is not allowed this request. |
| not_found | 404 | no | What the request names does not exist or is not public. |
| expired | 410 | no | What the request refers to is no longer available; ask for it afresh. |
| quota_exceeded | 429 | no | A request-rate limit was reached; error says which. Slow down before sending more. |
| cancelled | 499 | no | The caller closed or cancelled the request before it finished. The work was stopped and nothing was charged. |
| time_limit | 503 | yes | Your timeoutMs ran out before there was anything to return; ask again, or allow more time. |
| temporarily_unavailable | 503 | yes | Not available right now; ask again shortly. |
| store_unreachable | 503 | yes | Store routes (Shopify, WooCommerce): the store itself can’t be reached right now — it may be offline. Ask again later. Where a store route doesn’t say so yet, an offline store is temporarily_unavailable. |
| upstream_error | 502 | yes | The source answered in a way that could not be used; ask again later. |
| internal_error | 500 | yes | Something failed on our side; ask again later, and quote requestId if it persists. |
| tool_error | — | no | MCP only: a tool call failed in a way no other code describes; error says what. |
More codes may be added; treat one you do not recognise by its retryable flag. Over MCP, a tool error carries the same fields in structuredContent, and as a JSON text block beside the message.