{"openapi":"3.1.0","info":{"title":"flaky","version":"1.0.0","summary":"A mock REST API that fails on purpose.","description":"Real-looking data at real URLs, with the same resource shapes as JSONPlaceholder. The difference is that the caller decides how it behaves: _delay, _status and _fail_rate work on every endpoint, so a slow or broken backend is a query parameter rather than a local mock server.\n\nOut-of-range values are rejected with a 400 rather than ignored — a tool for testing failures should not fail quietly itself.","license":{"name":"MIT","identifier":"MIT"},"contact":{"url":"https://github.com/errohitrana2013/flaky/issues"}},"servers":[{"url":"https://flakyapi.dev/v1","description":"Production"}],"tags":[{"name":"users","description":"10 records"},{"name":"posts","description":"100 records"},{"name":"comments","description":"500 records"},{"name":"albums","description":"100 records"},{"name":"photos","description":"1000 records"},{"name":"todos","description":"200 records"},{"name":"products","description":"100 records"},{"name":"sandbox","description":"Writes that persist for 24 hours"},{"name":"custom","description":"Create a mock server from your own JSON or OpenAPI spec, live for 1 to 9 days"},{"name":"scenario","description":"Failure sequences that recover, for retry and circuit-breaker tests"},{"name":"account","description":"Keys and API description"}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Optional. Without a key you get the anonymous tier: 1000 requests/day. A free key raises that to 10000 and adds a sandbox."}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"status":{"type":"integer"},"message":{"type":"string"},"hint":{"type":"string","description":"What to do about it."}}}}}}},"paths":{"/users":{"get":{"summary":"List users","description":"10 records. Any field can also be used as a filter, e.g. ?userId=3.","tags":["users"],"parameters":[{"name":"_limit","in":"query","required":false,"description":"Page size. Capped at your tier's maximum rather than rejected.","schema":{"type":"integer","minimum":1,"example":10}},{"name":"_page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1}},{"name":"_start","in":"query","required":false,"description":"Offset, the json-server form. Wins over _page when both are given.","schema":{"type":"integer","minimum":0}},{"name":"_sort","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string"}},{"name":"_order","in":"query","required":false,"description":"asc (default) or desc.","schema":{"type":"string","enum":["asc","desc"]}},{"name":"_q","in":"query","required":false,"description":"Full-text search across the whole record.","schema":{"type":"string"}},{"name":"_select","in":"query","required":false,"description":"Comma-separated fields to return; id is always included. Also accepted as `select`, DummyJSON's spelling.","schema":{"type":"string","example":"title,price"}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"Matching records. x-total-count carries the unfiltered total.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"400":{"description":"A query parameter was out of range or not recognised.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Daily request limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a users record (echoed, not stored)","description":"Returns the record with an id, exactly as JSONPlaceholder does, and does not persist it. The response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["users"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"The echoed record."},"405":{"description":"PUT, PATCH and DELETE need a record id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/users/{id}":{"get":{"summary":"Get one users record","tags":["users"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":10}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"The record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"summary":"Replace a users record (echoed, not stored)","description":"Returns your body as the whole record, keeping the id. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["users"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"The echoed record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Change fields on a users record (echoed, not stored)","description":"Returns the record with your fields merged in. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["users"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"The echoed record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a users record (echoed, not stored)","description":"Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["users"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"{ deleted: true, id }"}}}},"/users/{id}/posts":{"get":{"summary":"List the posts belonging to a users","description":"Matches on posts.userId.","tags":["users"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"_limit","in":"query","required":false,"description":"Page size. Capped at your tier's maximum rather than rejected.","schema":{"type":"integer","minimum":1,"example":10}},{"name":"_page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1}},{"name":"_start","in":"query","required":false,"description":"Offset, the json-server form. Wins over _page when both are given.","schema":{"type":"integer","minimum":0}},{"name":"_sort","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string"}},{"name":"_order","in":"query","required":false,"description":"asc (default) or desc.","schema":{"type":"string","enum":["asc","desc"]}},{"name":"_q","in":"query","required":false,"description":"Full-text search across the whole record.","schema":{"type":"string"}},{"name":"_select","in":"query","required":false,"description":"Comma-separated fields to return; id is always included. Also accepted as `select`, DummyJSON's spelling.","schema":{"type":"string","example":"title,price"}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"Matching posts."}}},"post":{"summary":"Create one of a users's posts (echoed, not stored)","description":"posts.userId is taken from the path, whatever the body says. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["users"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"The echoed record."},"404":{"description":"No users with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/users/{id}/albums":{"get":{"summary":"List the albums belonging to a users","description":"Matches on albums.userId.","tags":["users"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"_limit","in":"query","required":false,"description":"Page size. Capped at your tier's maximum rather than rejected.","schema":{"type":"integer","minimum":1,"example":10}},{"name":"_page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1}},{"name":"_start","in":"query","required":false,"description":"Offset, the json-server form. Wins over _page when both are given.","schema":{"type":"integer","minimum":0}},{"name":"_sort","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string"}},{"name":"_order","in":"query","required":false,"description":"asc (default) or desc.","schema":{"type":"string","enum":["asc","desc"]}},{"name":"_q","in":"query","required":false,"description":"Full-text search across the whole record.","schema":{"type":"string"}},{"name":"_select","in":"query","required":false,"description":"Comma-separated fields to return; id is always included. Also accepted as `select`, DummyJSON's spelling.","schema":{"type":"string","example":"title,price"}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"Matching albums."}}},"post":{"summary":"Create one of a users's albums (echoed, not stored)","description":"albums.userId is taken from the path, whatever the body says. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["users"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"The echoed record."},"404":{"description":"No users with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/users/{id}/todos":{"get":{"summary":"List the todos belonging to a users","description":"Matches on todos.userId.","tags":["users"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"_limit","in":"query","required":false,"description":"Page size. Capped at your tier's maximum rather than rejected.","schema":{"type":"integer","minimum":1,"example":10}},{"name":"_page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1}},{"name":"_start","in":"query","required":false,"description":"Offset, the json-server form. Wins over _page when both are given.","schema":{"type":"integer","minimum":0}},{"name":"_sort","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string"}},{"name":"_order","in":"query","required":false,"description":"asc (default) or desc.","schema":{"type":"string","enum":["asc","desc"]}},{"name":"_q","in":"query","required":false,"description":"Full-text search across the whole record.","schema":{"type":"string"}},{"name":"_select","in":"query","required":false,"description":"Comma-separated fields to return; id is always included. Also accepted as `select`, DummyJSON's spelling.","schema":{"type":"string","example":"title,price"}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"Matching todos."}}},"post":{"summary":"Create one of a users's todos (echoed, not stored)","description":"todos.userId is taken from the path, whatever the body says. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["users"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"The echoed record."},"404":{"description":"No users with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/posts":{"get":{"summary":"List posts","description":"100 records. Any field can also be used as a filter, e.g. ?userId=3.","tags":["posts"],"parameters":[{"name":"_limit","in":"query","required":false,"description":"Page size. Capped at your tier's maximum rather than rejected.","schema":{"type":"integer","minimum":1,"example":10}},{"name":"_page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1}},{"name":"_start","in":"query","required":false,"description":"Offset, the json-server form. Wins over _page when both are given.","schema":{"type":"integer","minimum":0}},{"name":"_sort","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string"}},{"name":"_order","in":"query","required":false,"description":"asc (default) or desc.","schema":{"type":"string","enum":["asc","desc"]}},{"name":"_q","in":"query","required":false,"description":"Full-text search across the whole record.","schema":{"type":"string"}},{"name":"_select","in":"query","required":false,"description":"Comma-separated fields to return; id is always included. Also accepted as `select`, DummyJSON's spelling.","schema":{"type":"string","example":"title,price"}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"Matching records. x-total-count carries the unfiltered total.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"400":{"description":"A query parameter was out of range or not recognised.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Daily request limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a posts record (echoed, not stored)","description":"Returns the record with an id, exactly as JSONPlaceholder does, and does not persist it. The response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["posts"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"The echoed record."},"405":{"description":"PUT, PATCH and DELETE need a record id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/posts/{id}":{"get":{"summary":"Get one posts record","tags":["posts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"The record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"summary":"Replace a posts record (echoed, not stored)","description":"Returns your body as the whole record, keeping the id. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["posts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"The echoed record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Change fields on a posts record (echoed, not stored)","description":"Returns the record with your fields merged in. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["posts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"The echoed record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a posts record (echoed, not stored)","description":"Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["posts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"{ deleted: true, id }"}}}},"/posts/{id}/comments":{"get":{"summary":"List the comments belonging to a posts","description":"Matches on comments.postId.","tags":["posts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"_limit","in":"query","required":false,"description":"Page size. Capped at your tier's maximum rather than rejected.","schema":{"type":"integer","minimum":1,"example":10}},{"name":"_page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1}},{"name":"_start","in":"query","required":false,"description":"Offset, the json-server form. Wins over _page when both are given.","schema":{"type":"integer","minimum":0}},{"name":"_sort","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string"}},{"name":"_order","in":"query","required":false,"description":"asc (default) or desc.","schema":{"type":"string","enum":["asc","desc"]}},{"name":"_q","in":"query","required":false,"description":"Full-text search across the whole record.","schema":{"type":"string"}},{"name":"_select","in":"query","required":false,"description":"Comma-separated fields to return; id is always included. Also accepted as `select`, DummyJSON's spelling.","schema":{"type":"string","example":"title,price"}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"Matching comments."}}},"post":{"summary":"Create one of a posts's comments (echoed, not stored)","description":"comments.postId is taken from the path, whatever the body says. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["posts"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"The echoed record."},"404":{"description":"No posts with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/comments":{"get":{"summary":"List comments","description":"500 records. Any field can also be used as a filter, e.g. ?userId=3.","tags":["comments"],"parameters":[{"name":"_limit","in":"query","required":false,"description":"Page size. Capped at your tier's maximum rather than rejected.","schema":{"type":"integer","minimum":1,"example":10}},{"name":"_page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1}},{"name":"_start","in":"query","required":false,"description":"Offset, the json-server form. Wins over _page when both are given.","schema":{"type":"integer","minimum":0}},{"name":"_sort","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string"}},{"name":"_order","in":"query","required":false,"description":"asc (default) or desc.","schema":{"type":"string","enum":["asc","desc"]}},{"name":"_q","in":"query","required":false,"description":"Full-text search across the whole record.","schema":{"type":"string"}},{"name":"_select","in":"query","required":false,"description":"Comma-separated fields to return; id is always included. Also accepted as `select`, DummyJSON's spelling.","schema":{"type":"string","example":"title,price"}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"Matching records. x-total-count carries the unfiltered total.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"400":{"description":"A query parameter was out of range or not recognised.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Daily request limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a comments record (echoed, not stored)","description":"Returns the record with an id, exactly as JSONPlaceholder does, and does not persist it. The response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["comments"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"The echoed record."},"405":{"description":"PUT, PATCH and DELETE need a record id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/comments/{id}":{"get":{"summary":"Get one comments record","tags":["comments"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":500}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"The record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"summary":"Replace a comments record (echoed, not stored)","description":"Returns your body as the whole record, keeping the id. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["comments"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"The echoed record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Change fields on a comments record (echoed, not stored)","description":"Returns the record with your fields merged in. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["comments"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"The echoed record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a comments record (echoed, not stored)","description":"Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["comments"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"{ deleted: true, id }"}}}},"/albums":{"get":{"summary":"List albums","description":"100 records. Any field can also be used as a filter, e.g. ?userId=3.","tags":["albums"],"parameters":[{"name":"_limit","in":"query","required":false,"description":"Page size. Capped at your tier's maximum rather than rejected.","schema":{"type":"integer","minimum":1,"example":10}},{"name":"_page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1}},{"name":"_start","in":"query","required":false,"description":"Offset, the json-server form. Wins over _page when both are given.","schema":{"type":"integer","minimum":0}},{"name":"_sort","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string"}},{"name":"_order","in":"query","required":false,"description":"asc (default) or desc.","schema":{"type":"string","enum":["asc","desc"]}},{"name":"_q","in":"query","required":false,"description":"Full-text search across the whole record.","schema":{"type":"string"}},{"name":"_select","in":"query","required":false,"description":"Comma-separated fields to return; id is always included. Also accepted as `select`, DummyJSON's spelling.","schema":{"type":"string","example":"title,price"}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"Matching records. x-total-count carries the unfiltered total.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"400":{"description":"A query parameter was out of range or not recognised.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Daily request limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a albums record (echoed, not stored)","description":"Returns the record with an id, exactly as JSONPlaceholder does, and does not persist it. The response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["albums"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"The echoed record."},"405":{"description":"PUT, PATCH and DELETE need a record id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/albums/{id}":{"get":{"summary":"Get one albums record","tags":["albums"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"The record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"summary":"Replace a albums record (echoed, not stored)","description":"Returns your body as the whole record, keeping the id. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["albums"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"The echoed record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Change fields on a albums record (echoed, not stored)","description":"Returns the record with your fields merged in. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["albums"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"The echoed record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a albums record (echoed, not stored)","description":"Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["albums"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"{ deleted: true, id }"}}}},"/albums/{id}/photos":{"get":{"summary":"List the photos belonging to a albums","description":"Matches on photos.albumId.","tags":["albums"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}},{"name":"_limit","in":"query","required":false,"description":"Page size. Capped at your tier's maximum rather than rejected.","schema":{"type":"integer","minimum":1,"example":10}},{"name":"_page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1}},{"name":"_start","in":"query","required":false,"description":"Offset, the json-server form. Wins over _page when both are given.","schema":{"type":"integer","minimum":0}},{"name":"_sort","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string"}},{"name":"_order","in":"query","required":false,"description":"asc (default) or desc.","schema":{"type":"string","enum":["asc","desc"]}},{"name":"_q","in":"query","required":false,"description":"Full-text search across the whole record.","schema":{"type":"string"}},{"name":"_select","in":"query","required":false,"description":"Comma-separated fields to return; id is always included. Also accepted as `select`, DummyJSON's spelling.","schema":{"type":"string","example":"title,price"}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"Matching photos."}}},"post":{"summary":"Create one of a albums's photos (echoed, not stored)","description":"photos.albumId is taken from the path, whatever the body says. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["albums"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"The echoed record."},"404":{"description":"No albums with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/photos":{"get":{"summary":"List photos","description":"1000 records. Any field can also be used as a filter, e.g. ?userId=3.","tags":["photos"],"parameters":[{"name":"_limit","in":"query","required":false,"description":"Page size. Capped at your tier's maximum rather than rejected.","schema":{"type":"integer","minimum":1,"example":10}},{"name":"_page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1}},{"name":"_start","in":"query","required":false,"description":"Offset, the json-server form. Wins over _page when both are given.","schema":{"type":"integer","minimum":0}},{"name":"_sort","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string"}},{"name":"_order","in":"query","required":false,"description":"asc (default) or desc.","schema":{"type":"string","enum":["asc","desc"]}},{"name":"_q","in":"query","required":false,"description":"Full-text search across the whole record.","schema":{"type":"string"}},{"name":"_select","in":"query","required":false,"description":"Comma-separated fields to return; id is always included. Also accepted as `select`, DummyJSON's spelling.","schema":{"type":"string","example":"title,price"}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"Matching records. x-total-count carries the unfiltered total.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"400":{"description":"A query parameter was out of range or not recognised.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Daily request limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a photos record (echoed, not stored)","description":"Returns the record with an id, exactly as JSONPlaceholder does, and does not persist it. The response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["photos"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"The echoed record."},"405":{"description":"PUT, PATCH and DELETE need a record id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/photos/{id}":{"get":{"summary":"Get one photos record","tags":["photos"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":1000}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"The record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"summary":"Replace a photos record (echoed, not stored)","description":"Returns your body as the whole record, keeping the id. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["photos"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"The echoed record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Change fields on a photos record (echoed, not stored)","description":"Returns the record with your fields merged in. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["photos"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"The echoed record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a photos record (echoed, not stored)","description":"Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["photos"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"{ deleted: true, id }"}}}},"/todos":{"get":{"summary":"List todos","description":"200 records. Any field can also be used as a filter, e.g. ?userId=3.","tags":["todos"],"parameters":[{"name":"_limit","in":"query","required":false,"description":"Page size. Capped at your tier's maximum rather than rejected.","schema":{"type":"integer","minimum":1,"example":10}},{"name":"_page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1}},{"name":"_start","in":"query","required":false,"description":"Offset, the json-server form. Wins over _page when both are given.","schema":{"type":"integer","minimum":0}},{"name":"_sort","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string"}},{"name":"_order","in":"query","required":false,"description":"asc (default) or desc.","schema":{"type":"string","enum":["asc","desc"]}},{"name":"_q","in":"query","required":false,"description":"Full-text search across the whole record.","schema":{"type":"string"}},{"name":"_select","in":"query","required":false,"description":"Comma-separated fields to return; id is always included. Also accepted as `select`, DummyJSON's spelling.","schema":{"type":"string","example":"title,price"}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"Matching records. x-total-count carries the unfiltered total.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"400":{"description":"A query parameter was out of range or not recognised.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Daily request limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a todos record (echoed, not stored)","description":"Returns the record with an id, exactly as JSONPlaceholder does, and does not persist it. The response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["todos"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"The echoed record."},"405":{"description":"PUT, PATCH and DELETE need a record id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/todos/{id}":{"get":{"summary":"Get one todos record","tags":["todos"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":200}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"The record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"summary":"Replace a todos record (echoed, not stored)","description":"Returns your body as the whole record, keeping the id. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["todos"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"The echoed record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Change fields on a todos record (echoed, not stored)","description":"Returns the record with your fields merged in. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["todos"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"The echoed record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a todos record (echoed, not stored)","description":"Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["todos"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"{ deleted: true, id }"}}}},"/products":{"get":{"summary":"List products","description":"100 records. Any field can also be used as a filter, e.g. ?userId=3.","tags":["products"],"parameters":[{"name":"_limit","in":"query","required":false,"description":"Page size. Capped at your tier's maximum rather than rejected.","schema":{"type":"integer","minimum":1,"example":10}},{"name":"_page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1}},{"name":"_start","in":"query","required":false,"description":"Offset, the json-server form. Wins over _page when both are given.","schema":{"type":"integer","minimum":0}},{"name":"_sort","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string"}},{"name":"_order","in":"query","required":false,"description":"asc (default) or desc.","schema":{"type":"string","enum":["asc","desc"]}},{"name":"_q","in":"query","required":false,"description":"Full-text search across the whole record.","schema":{"type":"string"}},{"name":"_select","in":"query","required":false,"description":"Comma-separated fields to return; id is always included. Also accepted as `select`, DummyJSON's spelling.","schema":{"type":"string","example":"title,price"}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"Matching records. x-total-count carries the unfiltered total.","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","additionalProperties":true}}}}},"400":{"description":"A query parameter was out of range or not recognised.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Daily request limit reached.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a products record (echoed, not stored)","description":"Returns the record with an id, exactly as JSONPlaceholder does, and does not persist it. The response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["products"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"201":{"description":"The echoed record."},"405":{"description":"PUT, PATCH and DELETE need a record id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/products/{id}":{"get":{"summary":"Get one products record","tags":["products"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1,"maximum":100}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"The record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"put":{"summary":"Replace a products record (echoed, not stored)","description":"Returns your body as the whole record, keeping the id. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["products"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"The echoed record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"summary":"Change fields on a products record (echoed, not stored)","description":"Returns the record with your fields merged in. Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["products"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"The echoed record."},"404":{"description":"No record with that id.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"summary":"Delete a products record (echoed, not stored)","description":"Echoed, not stored: the response carries x-mock-write to say so. Use a sandbox for writes that stick.","tags":["products"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer","minimum":1}}],"responses":{"200":{"description":"{ deleted: true, id }"}}}},"/keys":{"post":{"summary":"Issue a free API key","description":"No confirmation email; the key is returned immediately. Asking again with the same address returns the key you already have rather than minting another.","tags":["account"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"The key this address already had."},"201":{"description":"A new key. Shown once."},"400":{"description":"The email was not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many new keys from this address today.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandbox":{"post":{"summary":"Create a sandbox where writes persist","description":"Needs a key. Lives 24 hours. Writes inside it are stored; everything outside is read-only.","tags":["sandbox"],"security":[{"bearerAuth":[]}],"responses":{"201":{"description":"The sandbox id and its base URL."},"403":{"description":"The anonymous tier does not include sandboxes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Your tier's sandbox allowance is already in use.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/sandbox/{sandboxId}/{resource}":{"get":{"summary":"List records in a sandbox","tags":["sandbox"],"parameters":[{"name":"sandboxId","in":"path","required":true,"schema":{"type":"string"}},{"name":"resource","in":"path","required":true,"schema":{"type":"string","enum":["users","posts","comments","albums","photos","todos","products"]}}],"responses":{"200":{"description":"Base data merged with your changes."},"410":{"description":"The sandbox expired.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"summary":"Create a record that persists","tags":["sandbox"],"parameters":[{"name":"sandboxId","in":"path","required":true,"schema":{"type":"string"}},{"name":"resource","in":"path","required":true,"schema":{"type":"string","enum":["users","posts","comments","albums","photos","todos","products"]}}],"responses":{"201":{"description":"The stored record."},"413":{"description":"Record larger than 64 KB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/scenario":{"post":{"summary":"Create a failure sequence, in either direction","description":"_fail_rate is random and cannot be asserted on; _status never changes. A scenario is a counter, which is what retry logic and circuit breakers actually need — fail twice and succeed on the third so a test can prove the backoff worked, or fail N consecutive times to force a breaker open and then let it close. Send `succeed` instead of `fail` to run it the other way round: work N times, then fail from there on, which is how a rate limit, a quota, a free trial and an expiring token all behave. Send one or the other, never both. Responses carry x-scenario-attempt.","tags":["scenario"],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"fail":{"type":"integer","minimum":0,"maximum":50,"default":2,"description":"Fail this many attempts, then succeed for good."},"succeed":{"type":"integer","minimum":0,"maximum":50,"description":"Succeed this many attempts, then fail for good. Mutually exclusive with fail."},"status":{"type":"integer","minimum":400,"maximum":599,"description":"The failure status. Defaults to 503 with fail, 429 with succeed."}}}}}},"responses":{"201":{"description":"The id, and how to use and reset it."},"400":{"description":"Bad policy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/scenario/{id}":{"get":{"summary":"How far through the sequence you are","tags":["scenario"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Attempts so far and whether the next one fails."},"404":{"description":"No such scenario.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/scenario/{id}/reset":{"post":{"summary":"Rewind the counter without spending an attempt","tags":["scenario"],"description":"For a test's beforeEach. The inline ?_scenario_reset=1 rewinds too, but that request then becomes attempt 1.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Rewound."},"404":{"description":"No such scenario.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/custom":{"post":{"summary":"Create a mock server from your own JSON","parameters":[{"name":"days","in":"query","description":"How many days it stays live before it is deleted. Anything outside 1–9 is a 400, not a quiet cap.","schema":{"type":"integer","minimum":1,"maximum":9,"default":1}}],"description":"Send an array, or an object whose values are arrays; each array becomes an endpoint. No account needed. Lives for ?days= days, 1 to 9 (default 1), then is deleted — export it to keep it. Every query and chaos parameter works on the result, which is the point: your shapes, failing how you ask.","tags":["custom"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"oneOf":[{"type":"array"},{"type":"object"}]}}}},"responses":{"201":{"description":"The id, the endpoints it created, and an export link."},"400":{"description":"Not valid JSON, no arrays in it to serve, or days outside 1–9.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Larger than 256 KB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many custom APIs from this address today.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/custom/openapi":{"post":{"summary":"Create a mock of your own API from its OpenAPI spec","description":"Send an OpenAPI 3 or Swagger 2.0 document as JSON. Each GET list endpoint, and its /{id} route, becomes a resource with ten generated records shaped by the spec — ids in order, foreign keys that point at records that exist, enums and examples respected — stored and served exactly like POST /custom, so every chaos parameter and every export works on it. The response names what the app's base URL should be swapped for, every route that was not mocked and why, and every way the mock differs from the spec. Writes are not mocked. YAML is refused with a hint; send JSON.","tags":["custom"],"parameters":[{"name":"days","in":"query","description":"How many days it stays live before it is deleted. Anything outside 1–9 is a 400, not a quiet cap.","schema":{"type":"integer","minimum":1,"maximum":9,"default":1}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"201":{"description":"The same answer as POST /custom, plus from, replaces, skipped and warnings."},"400":{"description":"Not JSON, not an OpenAPI document, YAML, nothing in it could be mocked, or days outside 1–9.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"Larger than 1 MB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Too many custom APIs from this address today.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/custom/{id}/{resource}":{"get":{"summary":"Read one of your own resources","description":"Filtering, sorting, paging, _select and every chaos parameter apply here exactly as they do to the built-in resources.","tags":["custom"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"resource","in":"path","required":true,"schema":{"type":"string"}},{"name":"_limit","in":"query","required":false,"description":"Page size. Capped at your tier's maximum rather than rejected.","schema":{"type":"integer","minimum":1,"example":10}},{"name":"_page","in":"query","required":false,"description":"1-based page number.","schema":{"type":"integer","minimum":1}},{"name":"_start","in":"query","required":false,"description":"Offset, the json-server form. Wins over _page when both are given.","schema":{"type":"integer","minimum":0}},{"name":"_sort","in":"query","required":false,"description":"Field to sort by.","schema":{"type":"string"}},{"name":"_order","in":"query","required":false,"description":"asc (default) or desc.","schema":{"type":"string","enum":["asc","desc"]}},{"name":"_q","in":"query","required":false,"description":"Full-text search across the whole record.","schema":{"type":"string"}},{"name":"_select","in":"query","required":false,"description":"Comma-separated fields to return; id is always included. Also accepted as `select`, DummyJSON's spelling.","schema":{"type":"string","example":"title,price"}},{"name":"_delay","in":"query","required":false,"description":"Stall this many milliseconds before responding, 0 to 10000. Anything else is a 400.","schema":{"type":"integer","minimum":0,"maximum":10000,"example":3000}},{"name":"_status","in":"query","required":false,"description":"Force this HTTP status. 100 to 599; 200 means behave normally. Anything else is a 400.","schema":{"type":"integer","minimum":100,"maximum":599,"example":503}},{"name":"_fail_rate","in":"query","required":false,"description":"Probability from 0 to 1 that this request returns 500. Rolled independently per request.","schema":{"type":"number","minimum":0,"maximum":1,"example":0.3}},{"name":"_retry_after","in":"query","required":false,"description":"Seconds to put in the Retry-After header. Only meaningful with _status=429 or 503, where it defaults to 5.","schema":{"type":"integer","minimum":0,"maximum":3600,"example":30}},{"name":"_scenario","in":"query","required":false,"description":"A scenario id from POST /v1/scenario. Runs a deterministic sequence: fail N times then recover (retries, circuit breakers), or succeed N times then fail (rate limits, quotas, expiring tokens).","schema":{"type":"string","pattern":"^[0-9a-f]{16}$"}},{"name":"_malformed","in":"query","required":false,"description":"Truncate the body mid-record, as a dropped connection would. Valid-looking JSON that stops — the .json() failure path.","schema":{"type":"integer","enum":[0,1]}},{"name":"_cors","in":"query","required":false,"description":"Set to `off` to omit the CORS headers, so a browser refuses the response cross-origin. From a server or curl it looks normal, because CORS is enforced by the browser.","schema":{"type":"string","enum":["off"]}}],"responses":{"200":{"description":"Your records."},"404":{"description":"No such API, or no such resource in it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"410":{"description":"It expired. Each lasts the days it was created for, 1 to 9.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/custom/{id}/export":{"get":{"summary":"Download it as files that run locally","description":"node, python, java and csharp each return a complete single-file server with your data embedded — no install, no network, and the chaos parameters work locally exactly as they do here, which json-server cannot do. The four servers answer identically, byte for byte. json-server returns a db.json; msw returns handlers for a test suite. All six work offline, in CI, and after this service is gone.","tags":["custom"],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"format","in":"query","schema":{"type":"string","enum":["node","python","java","csharp","json-server","msw"],"default":"json-server"}}],"responses":{"200":{"description":"A file, as an attachment."},"400":{"description":"Unknown format.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/meta":{"get":{"summary":"Machine-readable description of the API","description":"Resource names, row counts, tier limits, and which tier you are on.","tags":["account"],"responses":{"200":{"description":"The description."}}}}}}