{"ns":"echo","title":"Request Echo","version":"1.0","summary":"Request Echo is an HTTP request and response service for API testing. Its endpoints reflect the arguments, headers, body, form fields and cookies they receive, or produce a requested status code, delay, redirect chain, cookie or authentication challenge. A playground builds requests from method, path, query and header grids and several body types, and keeps saved requests per browser tab.","problem":"People who test HTTP clients need a service that answers exactly what was sent or exactly what was asked for. If an echo drops a value, a status is ignored or an auth check is lax, tests built on it give wrong confidence.","personas":[{"name":"API tester","need":"Send requests and trust that the response reflects them faithfully."},{"name":"Client developer","need":"Get predictable statuses, redirects, delays, cookies and auth challenges to exercise error handling."}],"goals":["Every echo endpoint reflects what was sent without loss or change.","Status, delay, redirect, cookie and auth endpoints behave exactly as requested.","The playground builds the same request the user sees in its grids.","Invalid input is caught before sending where possible."],"features":[{"id":"ECH","title":"Echo endpoints","summary":"Method endpoints and /anything reflect the request.","reqs":[{"id":"FR-ECH-01","weight":"Must","text":"GET /get answers 200 with a JSON body that reflects the query arguments, the URL and the request headers.","accept":["GET /get?keval=run-4821 returns 200 with Content-Type application/json and args.keval equal to \"run-4821\".","url ends with /api/echo/get?keval=run-4821 and headers contains Host."]},{"id":"FR-ECH-02","weight":"Must","text":"A query key that appears more than once is echoed as a list of all its values in order.","accept":["/get?keval=demo-123&tag=a&tag=b returns args.keval \"demo-123\" and args.tag [\"a\",\"b\"]."]},{"id":"FR-ECH-03","weight":"Must","text":"A JSON body is echoed parsed in \"json\" and raw in \"data\", with its method and headers.","accept":["POST {\"item\":\"Notebook\",\"qty\":3,\"gift\":false} returns json equal to that object.","The X-Request-Id header sent is echoed in headers; headers[\"Content-Type\"] is application/json; method \"POST\"."]},{"id":"FR-ECH-04","weight":"Should","text":"Urlencoded form bodies arrive in \"form\" and leave \"json\" null.","accept":["PUT with city=Lyon and zip=69001 returns form {\"city\":\"Lyon\",\"zip\":\"69001\"}, json null, method \"PUT\" and Content-Type application/x-www-form-urlencoded."]},{"id":"FR-ECH-05","weight":"Should","text":"Each method endpoint and /anything echo the method, body, arguments and URL; an endpoint that accepts only one method answers 404 to others.","accept":["DELETE /delete with {\"id\":7} returns 200, json {\"id\":7} and method \"DELETE\".","PATCH /anything/a/b?x=1 with text \"hi\" returns data \"hi\", args {\"x\":\"1\"} and a url ending /anything/a/b?x=1.","GET /post returns 404.","GET /base64/UmVxdWVzdCBFY2hvIHJvY2tz returns 200 with the text \"Request Echo rocks\"."]}]},{"id":"STA","title":"Status, redirects and delay","summary":"Endpoints produce a chosen status code, a redirect chain or a delayed answer.","reqs":[{"id":"FR-STA-01","weight":"Must","text":"The status endpoint answers exactly the requested code, and an invalid code is refused.","accept":["/status/201 shows \"201 Created\"; /status/404 shows \"404 Not Found\" with an empty body.","/status/418 shows \"418 I'm a teapot\" with ASCII art containing \"teapot\"; /status/503 shows \"503 Service Unavailable\".","/status/abc returns 400 with the message \"Invalid status code\"."]},{"id":"FR-STA-02","weight":"Should","text":"A redirect chain is followed to /get and the final response reports that it was redirected.","accept":["\"Redirect ×3\" ends 200 OK with \"Redirected → /api/echo/get\" and a body url ending /api/echo/get."]},{"id":"FR-STA-03","weight":"Should","text":"The delay endpoint waits the requested time before answering, and the client shows it is waiting.","accept":["The send button shows \"Sending…\" for about 2 seconds on a 2 second delay.","The answer is 200 OK and the displayed time is at least 2000 ms."]}]},{"id":"AUT","title":"Authentication challenges","summary":"Basic and Bearer endpoints check the credentials sent.","reqs":[{"id":"FR-AUT-01","weight":"Must","text":"The Basic auth endpoint accepts only the user and password named in its path; a wrong password or a missing Authorization header gives 401 with an empty body.","accept":["Correct credentials return 200 {\"authenticated\":true,\"user\":<the path user>}.","A wrong password returns 401 Unauthorized with an empty body.","Removing the Authorization header returns 401 Unauthorized."]},{"id":"FR-AUT-02","weight":"Should","text":"The Bearer endpoint echoes the token of a Bearer authorization and refuses any other scheme with 401.","accept":["Authorization \"Bearer <token>\" returns 200 {\"authenticated\":true,\"token\":<that token>}.","Authorization \"Token abc\" returns 401 Unauthorized."]}]},{"id":"COK","title":"Cookies and response headers","summary":"Endpoints set and delete cookies and set response headers from the query.","reqs":[{"id":"FR-COK-01","weight":"Should","text":"Cookies set through the cookie endpoints persist in the browser and can be deleted by name.","accept":["/cookies/set?flavor=oat&size=xl answers 200 after a redirect with cookies {\"flavor\":\"oat\",\"size\":\"xl\"}.","/cookies still lists flavor and size.","/cookies/delete?flavor leaves cookies {\"size\":\"xl\"}."]},{"id":"FR-COK-02","weight":"Could","text":"Response headers requested in the query are returned and visible in the response Headers tab.","accept":["/response-headers?x-trace=abc123 answers 200 OK and the Headers tab lists x-trace with value abc123."]}]},{"id":"PLY","title":"Playground","summary":"The playground builds requests from grids and body editors and saves them per tab.","reqs":[{"id":"FR-PLY-01","weight":"Must","text":"The Params grid and the Path stay in sync in both directions, with values correctly encoded and disabled rows left out.","accept":["Path /get?color=teal&size=9 shows Params badge 2 with rows color=teal and size=9.","Unticking size makes the Path /get?color=teal and the badge 1.","Adding key \"note\" with value \"a b&c\" makes the Path /get?color=teal&note=a%20b%26c; the response args is {\"color\":\"teal\",\"note\":\"a b&c\"}."]},{"id":"FR-PLY-02","weight":"Should","text":"Malformed JSON is refused before sending with a message starting \"Body is not valid JSON:\" and no response is shown; a valid body sends.","accept":["{\"a\": 1,} shows \"Body is not valid JSON:\" and no new response.","{\"a\": 1} returns 200 OK with json {\"a\":1}."]},{"id":"FR-PLY-03","weight":"Could","text":"GET sends no body: the body settings state this and are disabled.","accept":["The Body tab shows \"GET requests are sent without a body.\" and the body type radios are disabled."]},{"id":"FR-PLY-04","weight":"Could","text":"Saved requests persist across reloads for the tab, can be deleted and cannot reuse an existing name.","accept":["Saving \"Orders lookup\" lists it last among 9 saved requests.","Saving again as \"teapot\" shows \"A saved request named \\\"teapot\\\" already exists\".","After a reload \"Orders lookup\" is still listed; deleting it leaves 8."]}]},{"id":"FRM","title":"HTML order form and catalogue","summary":"A classic HTML order form posts to the echo, and a catalogue lists the endpoints.","reqs":[{"id":"FR-FRM-01","weight":"Must","text":"The HTML order form submits urlencoded fields to /api/echo/post, with repeated checkboxes arriving as a list.","accept":["Submitting shows JSON whose form has custname \"Rae Kim\", size \"medium\", topping [\"bacon\",\"mushroom\"], delivery \"19:30\" and comments \"Ring twice\"."]},{"id":"FR-FRM-02","weight":"Could","text":"The preferred delivery time must be between 11:00 and 21:00 in 15-minute steps; the browser blocks other values.","accept":["22:00 is blocked with a \"must be 21:00 or earlier\" style message.","19:10 is blocked because it is not on a 15-minute step."]},{"id":"FR-FRM-03","weight":"Could","text":"The endpoint catalogue lists every endpoint and can be filtered by group and search text.","accept":["The page reads \"21 endpoints\".","Group \"Auth\" shows \"2 endpoints\": /basic-auth/{user}/{passwd} and /bearer.","All groups with search \"cookie\" shows \"3 endpoints\"."]}]}],"nfr":[{"id":"NFR-01","weight":"Must","text":"An echo never drops, reorders or alters data it was sent.","accept":["Repeated keys, JSON bodies and form fields round-trip unchanged."]},{"id":"NFR-02","weight":"Should","text":"A slow response is awaited by the playground and is not treated as a failure."}],"glossary":[{"term":"Echo","meaning":"A response that reflects the request that was received."},{"term":"Saved request","meaning":"A playground request stored for the current browser tab."}],"outOfScope":["File uploads","Streaming and gzip endpoints"]}