Free test API for REST, GraphQL, gRPC and WebSocket

Real endpoints you can hit right now on playground.tetiva.app. It answers with what you sent, any status you ask for, as slowly as you like. No key, nothing stored, not a proxy.

What do you want to test?

Not here? See all 22 requests below or ask us to add yours

  • RESThttps://playground.tetiva.app
  • GraphQLhttps://playground.tetiva.app/graphql
  • WebSocketwss://playground.tetiva.app/ws
  • gRPCreflection onplayground.tetiva.app:443
  • Not a proxy — playground.tetiva.app never calls other hosts
  • Nothing kept — bodies aren't stored or logged
  • Fair use — 60 req/min, 1 MB bodies, delays up to 10 s

Every endpoint

One host, four protocols, 22 requests. Everything below works from curl, your own code, Postman or Tetiva: REST on https://playground.tetiva.app, GraphQL at /graphql, WebSocket at wss://playground.tetiva.app/ws and gRPC on playground.tetiva.app:443 with server reflection.

REST · Echo

Every echo answers with the method, URL, query (as args), headers and body it received, plus a request_id. A JSON body comes back raw in data and parsed in json; forms and multipart uploads come back in form and files.

Echo a GET#

GEThttps://playground.tetiva.app/get?source=docs&page=2

Returns your request as JSON: query parameters in args, headers in headers, your IP in origin.

Query parameters · 2
sourcedocs
page2
cURL
curl 'https://playground.tetiva.app/get?source=docs&page=2'
Response examples
200Echo

200OK

Headers (6)
content-typeapplication/json; charset=utf-8
content-length314
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
{
  "method": "GET",
  "url": "https://playground.tetiva.app/get?source=docs&page=2",
  "args": {
    "page": "2",
    "source": "docs"
  },
  "headers": {
    "Host": "playground.tetiva.app",
    "User-Agent": "curl/8.7.1"
  },
  "origin": "203.0.113.42",
  "request_id": "00000000-0000-4000-8000-000000000001"
}

Echo my POST body#

POSThttps://playground.tetiva.app/post

Send a JSON body and get it back: raw in data, parsed in json. Bodies go up to 1 MB; forms and multipart files come back in form and files.

Headers · 1
Content-Typeapplication/json
Body · JSON
{"hello":"world"}
cURL
curl -X POST 'https://playground.tetiva.app/post' \
  -H 'Content-Type: application/json' \
  --data-raw '{"hello":"world"}'
Response examples
200Echo

200OK

Headers (6)
content-typeapplication/json; charset=utf-8
content-length397
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
{
  "method": "POST",
  "url": "https://playground.tetiva.app/post",
  "args": {},
  "headers": {
    "Content-Length": "17",
    "Content-Type": "application/json",
    "Host": "playground.tetiva.app",
    "User-Agent": "curl/8.7.1"
  },
  "data": "{\"hello\":\"world\"}",
  "json": {
    "hello": "world"
  },
  "origin": "203.0.113.42",
  "request_id": "00000000-0000-4000-8000-000000000001"
}

Echo a PUT#

PUThttps://playground.tetiva.app/put

The same echo for PUT: the body comes back in data and json.

Headers · 1
Content-Typeapplication/json
Body · JSON
{
  "id": 42,
  "name": "Ada Lovelace",
  "role": "admin"
}
cURL
curl -X PUT 'https://playground.tetiva.app/put' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "id": 42,
  "name": "Ada Lovelace",
  "role": "admin"
}'
Response examples
200Echo

200OK

Headers (6)
content-typeapplication/json; charset=utf-8
content-length488
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
{
  "method": "PUT",
  "url": "https://playground.tetiva.app/put",
  "args": {},
  "headers": {
    "Content-Length": "59",
    "Content-Type": "application/json",
    "Host": "playground.tetiva.app",
    "User-Agent": "curl/8.7.1"
  },
  "data": "{\n  \"id\": 42,\n  \"name\": \"Ada Lovelace\",\n  \"role\": \"admin\"\n}",
  "json": {
    "id": 42,
    "name": "Ada Lovelace",
    "role": "admin"
  },
  "origin": "203.0.113.42",
  "request_id": "00000000-0000-4000-8000-000000000001"
}

Echo a PATCH#

PATCHhttps://playground.tetiva.app/patch

The same echo for PATCH, handy for partial updates.

Headers · 1
Content-Typeapplication/json
Body · JSON
{
  "role": "viewer"
}
cURL
curl -X PATCH 'https://playground.tetiva.app/patch' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "role": "viewer"
}'
Response examples
200Echo

200OK

Headers (6)
content-typeapplication/json; charset=utf-8
content-length406
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
{
  "method": "PATCH",
  "url": "https://playground.tetiva.app/patch",
  "args": {},
  "headers": {
    "Content-Length": "22",
    "Content-Type": "application/json",
    "Host": "playground.tetiva.app",
    "User-Agent": "curl/8.7.1"
  },
  "data": "{\n  \"role\": \"viewer\"\n}",
  "json": {
    "role": "viewer"
  },
  "origin": "203.0.113.42",
  "request_id": "00000000-0000-4000-8000-000000000001"
}

Echo a DELETE#

DELETEhttps://playground.tetiva.app/delete?id=42

The same echo for DELETE. A body is allowed and comes back too.

Query parameters · 1
id42
cURL
curl -X DELETE 'https://playground.tetiva.app/delete?id=42'
Response examples
200Echo

200OK

Headers (6)
content-typeapplication/json; charset=utf-8
content-length314
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
{
  "method": "DELETE",
  "url": "https://playground.tetiva.app/delete?id=42",
  "args": {
    "id": "42"
  },
  "headers": {
    "Host": "playground.tetiva.app",
    "User-Agent": "curl/8.7.1"
  },
  "data": "",
  "json": null,
  "origin": "203.0.113.42",
  "request_id": "00000000-0000-4000-8000-000000000001"
}

Any method, any path#

POSThttps://playground.tetiva.app/anything/orders/42?dry_run=true

/anything and every path under it echo any method, custom ones included, so you can stand in for a route that doesn't exist yet.

Query parameters · 1
dry_runtrue
Headers · 1
Content-Typeapplication/json
Body · JSON
{
  "status": "shipped"
}
cURL
curl -X POST 'https://playground.tetiva.app/anything/orders/42?dry_run=true' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "status": "shipped"
}'
Response examples
200Echo

200OK

Headers (6)
content-typeapplication/json; charset=utf-8
content-length462
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
{
  "method": "POST",
  "url": "https://playground.tetiva.app/anything/orders/42?dry_run=true",
  "args": {
    "dry_run": "true"
  },
  "headers": {
    "Content-Length": "25",
    "Content-Type": "application/json",
    "Host": "playground.tetiva.app",
    "User-Agent": "curl/8.7.1"
  },
  "data": "{\n  \"status\": \"shipped\"\n}",
  "json": {
    "status": "shipped"
  },
  "origin": "203.0.113.42",
  "request_id": "00000000-0000-4000-8000-000000000001"
}

See my headers#

GEThttps://playground.tetiva.app/headers

Returns only the headers the server received. Useful to check what your client, proxy or SDK adds.

Headers · 1
X-Debug1
cURL
curl 'https://playground.tetiva.app/headers' \
  -H 'X-Debug: 1'
Response examples
200Your headers

200OK

Headers (6)
content-typeapplication/json; charset=utf-8
content-length167
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
{
  "headers": {
    "Host": "playground.tetiva.app",
    "User-Agent": "curl/8.7.1",
    "X-Debug": "1"
  },
  "request_id": "00000000-0000-4000-8000-000000000001"
}

REST · Status codes and delays

Make the server fail or stall on purpose: any status from 200 to 599, or a wait of up to 10 seconds.

Any status code#

GEThttps://playground.tetiva.app/status/418

Answers with the status in the path, from 200 to 599. Redirects point to /get, 401 adds WWW-Authenticate, 429 and 503 add Retry-After.

cURL
curl 'https://playground.tetiva.app/status/418'
Response examples
418I'm a teapot

418I'm a teapot

Headers (6)
content-typeapplication/json; charset=utf-8
content-length104
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
{
  "status": 418,
  "reason": "I'm a teapot",
  "request_id": "00000000-0000-4000-8000-000000000001"
}

Slow response#

GEThttps://playground.tetiva.app/delay/2

Waits the number of seconds in the path, up to 10, then answers; fractions work too, as in /delay/0.5. Good for testing timeouts and loading states.

cURL
curl 'https://playground.tetiva.app/delay/2'
Response examples
200After 2 seconds

200OK

Headers (6)
content-typeapplication/json; charset=utf-8
content-length237
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
{
  "method": "GET",
  "url": "https://playground.tetiva.app/delay/2",
  "args": {},
  "delay": 2,
  "started_at": "2026-10-17T09:30:00Z",
  "finished_at": "2026-10-17T09:30:02Z",
  "request_id": "00000000-0000-4000-8000-000000000001"
}

REST · Auth

Check that your client sends credentials, and that it copes with the 401 when it doesn't.

Bearer auth#

GEThttps://playground.tetiva.app/bearer

Any non-empty token passes and comes back in token. Without Authorization: Bearer … you get 401 with WWW-Authenticate: Bearer.

Authorization

Bearer

prefixBearer
tokentest-token
cURL
curl 'https://playground.tetiva.app/bearer' \
  -H 'Authorization: Bearer test-token'
Response examples
200With a token

200OK

Headers (6)
content-typeapplication/json; charset=utf-8
content-length109
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
{
  "authenticated": true,
  "token": "test-token",
  "request_id": "00000000-0000-4000-8000-000000000001"
}

Basic auth#

GEThttps://playground.tetiva.app/basic-auth/tetiva/playground

Passes when the Basic credentials match the user and password in the path, here tetiva / playground. Anything else gets 401.

Authorization

Basic

passwordplayground
usernametetiva
cURL
curl 'https://playground.tetiva.app/basic-auth/tetiva/playground' \
  -H 'Authorization: Basic dGV0aXZhOnBsYXlncm91bmQ='
Response examples
200Matching credentials

200OK

Headers (6)
content-typeapplication/json; charset=utf-8
content-length104
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
{
  "authenticated": true,
  "user": "tetiva",
  "request_id": "00000000-0000-4000-8000-000000000001"
}

REST · Fake data

Five fake users for lists, tables and pagination. The data is the same every time, and POST /users answers as if it worked but stores nothing.

Fake users#

GEThttps://playground.tetiva.app/users?limit=3

A page of fake users. limit takes 1 to 5, and next links to the following page until it is null.

Query parameters · 1
limit3
cURL
curl 'https://playground.tetiva.app/users?limit=3'
Response examples
200First page

200OK

Headers (6)
content-typeapplication/json; charset=utf-8
content-length576
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
{
  "data": [
    {
      "id": 1,
      "name": "Mila Novak",
      "email": "mila.novak@example.com",
      "role": "admin",
      "created_at": "2026-03-02T09:14:00Z"
    },
    {
      "id": 2,
      "name": "Jonas Berg",
      "email": "jonas.berg@example.com",
      "role": "editor",
      "created_at": "2026-04-18T16:40:00Z"
    },
    {
      "id": 3,
      "name": "Priya Raman",
      "email": "priya.raman@example.com",
      "role": "viewer",
      "created_at": "2026-05-27T11:05:00Z"
    }
  ],
  "total": 5,
  "limit": 3,
  "next": "/users?limit=3&after=3"
}

One fake user#

GEThttps://playground.tetiva.app/users/2

One user by id, from 1 to 5. Any other id gets 404 with a hint.

cURL
curl 'https://playground.tetiva.app/users/2'
Response examples
200Found

200OK

Headers (6)
content-typeapplication/json; charset=utf-8
content-length135
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
{
  "id": 2,
  "name": "Jonas Berg",
  "email": "jonas.berg@example.com",
  "role": "editor",
  "created_at": "2026-04-18T16:40:00Z"
}

Create a user#

POSThttps://playground.tetiva.app/users

Checks name and email and answers 201 with the new user and a Location header. Nothing is stored: stored is false.

Headers · 1
Content-Typeapplication/json
Body · JSON
{
  "name": "Grace Hopper",
  "email": "grace@example.com",
  "role": "editor"
}
cURL
curl -X POST 'https://playground.tetiva.app/users' \
  -H 'Content-Type: application/json' \
  --data-raw '{
  "name": "Grace Hopper",
  "email": "grace@example.com",
  "role": "editor"
}'
Response examples
201Created, not stored

201Created

Headers (7)
content-typeapplication/json; charset=utf-8
content-length151
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
locationhttps://playground.tetiva.app/users/6
{
  "id": 6,
  "name": "Grace Hopper",
  "email": "grace@example.com",
  "role": "editor",
  "created_at": "2026-10-17T09:30:00Z",
  "stored": false
}

GraphQL

A small library at https://playground.tetiva.app/graphql: books, authors and one mutation. Introspection is on, so any GraphQL client can load the schema.

Run a GraphQL query#

GraphQLhttps://playground.tetiva.app/graphql

Operation: Books

Lists books with their authors. first takes 1 to 50; a field that doesn't exist gets an error that lists the real ones.

Query
query Books($first: Int) {
  books(first: $first) {
    id
    title
    author {
      name
    }
  }
}
Variables
{
  "first": 2
}
cURL
curl -X POST 'https://playground.tetiva.app/graphql' \
  -H 'Content-Type: application/json' \
  --data-raw '{"query":"query Books($first: Int) {\n  books(first: $first) {\n    id\n    title\n    author {\n      name\n    }\n  }\n}","variables":{"first":2},"operationName":"Books"}'
Response examples
200Two books

200OK

Headers (6)
content-typeapplication/json; charset=utf-8
content-length304
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
{
  "data": {
    "books": [
      {
        "id": "1",
        "title": "Frankenstein",
        "author": {
          "name": "Mary Shelley"
        }
      },
      {
        "id": "2",
        "title": "Moby-Dick",
        "author": {
          "name": "Herman Melville"
        }
      }
    ]
  }
}

One book by id#

GraphQLhttps://playground.tetiva.app/graphql

Operation: Book

Fetches one book. An unknown id returns "book": null, not an error.

Query
query Book($id: ID!) {
  book(id: $id) {
    title
    year
    author {
      name
    }
  }
}
Variables
{
  "id": "3"
}
cURL
curl -X POST 'https://playground.tetiva.app/graphql' \
  -H 'Content-Type: application/json' \
  --data-raw '{"query":"query Book($id: ID!) {\n  book(id: $id) {\n    title\n    year\n    author {\n      name\n    }\n  }\n}","variables":{"id":"3"},"operationName":"Book"}'
Response examples
200One book

200OK

Headers (6)
content-typeapplication/json; charset=utf-8
content-length151
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
{
  "data": {
    "book": {
      "title": "The Time Machine",
      "year": 1895,
      "author": {
        "name": "H. G. Wells"
      }
    }
  }
}

Add a book#

GraphQLhttps://playground.tetiva.app/graphql

Operation: AddBook

A mutation that returns the new book with id 6. Nothing is stored: extensions.stored is false.

Query
mutation AddBook($title: String!, $authorId: ID!) {
  addBook(title: $title, authorId: $authorId) {
    id
    title
    author {
      name
    }
  }
}
Variables
{
  "title": "Carmilla",
  "authorId": "a5"
}
cURL
curl -X POST 'https://playground.tetiva.app/graphql' \
  -H 'Content-Type: application/json' \
  --data-raw '{"query":"mutation AddBook($title: String!, $authorId: ID!) {\n  addBook(title: $title, authorId: $authorId) {\n    id\n    title\n    author {\n      name\n    }\n  }\n}","variables":{"title":"Carmilla","authorId":"a5"},"operationName":"AddBook"}'
Response examples
200Added, not stored

200OK

Headers (6)
content-typeapplication/json; charset=utf-8
content-length191
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
{
  "data": {
    "addBook": {
      "id": "6",
      "title": "Carmilla",
      "author": {
        "name": "Sheridan Le Fanu"
      }
    }
  },
  "extensions": {
    "stored": false
  }
}

gRPC

echo.v1.EchoService on playground.tetiva.app:443 with TLS and server reflection, so clients find the methods without a .proto file.

Reflection

grpcurl playground.tetiva.app:443 list
grpcurl playground.tetiva.app:443 describe echo.v1.EchoService

Echo (unary)#

gRPCgrpcs://playground.tetiva.app

echo.v1.EchoService / Echo

Returns your text with seq, server_time and the request metadata as the server saw it.

Message
{
  "text": "ping"
}
gRPCurl
grpcurl -d '{
  "text": "ping"
}' \
  'playground.tetiva.app:443' \
  'echo.v1.EchoService/Echo'
Response examples
0Reply

0OK

Headers (4)
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
{
  "text": "ping",
  "seq": 1,
  "serverTime": "2026-10-17T09:30:00Z",
  "metadata": {
    "host": "playground.tetiva.app",
    "user-agent": "grpcurl/1.9.3 grpc-go/1.73.0"
  }
}

Stream from gRPC#

gRPCgrpcs://playground.tetiva.app

echo.v1.EchoService / ServerStream

Sends count messages (1 to 100, default 5), one every interval_ms (50 to 5000, default 500). The test API page runs it over gRPC-Web.

Message
{
  "text": "ping",
  "count": 3,
  "interval_ms": 500
}
gRPCurl
grpcurl -d '{
  "text": "ping",
  "count": 3,
  "interval_ms": 500
}' \
  'playground.tetiva.app:443' \
  'echo.v1.EchoService/ServerStream'
Response examples
0Stream log

0OK

Headers (4)
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
+0.000s CALL echo.v1.EchoService/ServerStream {"text":"ping","count":3,"interval_ms":500}
+0.000s RECV #1 {"text":"ping","seq":1,"serverTime":"2026-10-17T09:30:00Z"}
+0.500s RECV #2 {"text":"ping","seq":2,"serverTime":"2026-10-17T09:30:00.500Z"}
+1.000s RECV #3 {"text":"ping","seq":3,"serverTime":"2026-10-17T09:30:01Z"}
+1.000s END OK

Chat (bidirectional)#

gRPCgrpcs://playground.tetiva.app

echo.v1.EchoService / Chat

Every message you send comes straight back with the next seq, up to 100 a stream. Run it with grpcurl: the page's gRPC-Web transport can't stream both ways.

Message
{
  "text": "hello"
}
gRPCurl
grpcurl -d '{
  "text": "hello"
}' \
  'playground.tetiva.app:443' \
  'echo.v1.EchoService/Chat'
Response examples
0Chat log

0OK

Headers (4)
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
+0.000s CALL echo.v1.EchoService/Chat {"text":"hello"}
+0.000s RECV #1 {"text":"hello","seq":1,"serverTime":"2026-10-17T09:30:00Z"}
+0.000s END OK

WebSocket

Text and binary frames up to 64 KB. /ws closes after 60 seconds without a message, with 1000 Normal Closure; every session ends after 10 minutes.

Echo over WebSocket#

WebSocketwss://playground.tetiva.app/ws

Every frame you send comes straight back, text or binary, up to 64 KB a frame.

Messages · 1

helloText

hello
websocat
printf '%s' 'hello' | websocat 'wss://playground.tetiva.app/ws'
Response examples
101Session log

101Switching Protocols

Headers (4)
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
+0.000s → SENT hello
+0.000s ← RECV hello
+0.000s · INFO closed 1000 Normal Closure

Ticker#

WebSocketwss://playground.tetiva.app/ws/ticker

Pushes {"tick": n, "at": …} every second until you disconnect. Nothing to send.

websocat
websocat 'wss://playground.tetiva.app/ws/ticker'
Response examples
101Three ticks

101Switching Protocols

Headers (4)
x-request-id00000000-0000-4000-8000-000000000001
access-control-allow-origin*
ratelimit-limit60
ratelimit-remaining59
+1.000s ← RECV {"tick":1,"at":"2026-10-17T09:30:01Z"}
+2.000s ← RECV {"tick":2,"at":"2026-10-17T09:30:02Z"}
+3.000s ← RECV {"tick":3,"at":"2026-10-17T09:30:03Z"}
+3.000s · INFO closed 1000 Normal Closure

Missing an endpoint or protocol? Ask us to add it

Take the requests with you

The whole Playground as a ready-made collection, with example responses. Import it into Postman or Insomnia, or open it in Tetiva, a free open-source desktop API client.

  • Download Postman collectionPostman v2.1 · REST and GraphQL, 17 requests — works in Postman and Insomnia
  • Download Tetiva JSONAll four protocols, 20 requests — opens in Tetiva 1.2+
  • Straight into the app, with history, environments and tests. Tetiva 1.2+, gRPC needs 1.3+
  • Prefer your terminal?
    curl https://playground.tetiva.app/get