turlte API

Control a turtle on a shared canvas by calling an HTTP API. Three tiers unlock progressively more power.

Overview

Every device that calls the API gets its own turtle on the canvas, identified by its IP address. The turtle leaves a coloured trail as it moves. The more expressive the communication method you use, the more control you have.

TierMethodWhat you unlock
1 GET /api Move the turtle. Trail drawn in default colour.
2 POST /api Set trail colour & width, give your turtle a name.
3 WebSocket /ws Full Logo-style control: move, turn, pen up/down, coin spray. 👑 crown appears.
Canvas URL: open /canvas in a separate window to watch your turtle appear in real time.

The canvas

The virtual coordinate space is 1000 × 600 units. The origin (0, 0) is the top-left corner. x increases to the right, y increases downward — standard screen coordinates. The canvas scales to fill the viewer's browser window, so coordinates are always consistent regardless of screen size.

New turtles spawn at a random position. When a turtle moves via GET or POST, it automatically rotates to face the direction of travel.

Colours are specified as a hue value from 0 to 255 using maximum saturation and brightness (HSB colour model). Some reference points:

huecolour
0red
43orange
64yellow
85green
128cyan
170blue
213magenta
255red (full cycle)

Tier 1 — GET simplest

GET https://turlte.nl/api?x=number&y=number

Send a GET request with x and y query parameters. Your turtle appears at that position. Call it again with a new position and a trail is drawn between the two points.

Parameters

NameRequiredTypeRangeDescription
x✅ yesnumber0 – 1000Horizontal position
y✅ yesnumber0 – 600Vertical position

Try it in the browser

Paste this into your browser's address bar and press Enter:

https://turlte.nl/api?x=200&y=300

curl example

curl "https://turlte.nl/api?x=200&y=300"

JavaScript fetch example

const res  = await fetch('https://turlte.nl/api?x=200&y=300');
const data = await res.json();
console.log(data);

Response

{
  "ok":    true,
  "state": {
    "id":      "a3f8c2d1e4b9",  // your anonymous ID (hashed from IP)
    "name":    "a3f8c2d1e4b9",  // default name = your ID
    "x":       200,
    "y":       300,
    "heading": 0,
    "hue":     0,     // red by default
    "width":   2,
    "penDown": true,
    "tier":    "get"
  }
}

Tier 2 — POST more control

POST https://turlte.nl/api   Content-Type: application/json

Send a POST request with a JSON body. This unlocks trail colour, trail width, and the ability to name your turtle.

Request body fields

FieldRequiredTypeRangeDescription
x at least one field requirednumber0 – 1000Horizontal position
y number0 – 600Vertical position
hue integer0 – 255Trail colour (HSB hue)
widthinteger1 – 16Trail stroke width in pixels
name stringmax 16 charsTurtle display name
You can include any combination of fields. Send only name to rename without moving; send only hue to change colour for future trail segments without moving either.

curl example

curl -X POST https://turlte.nl/api \
  -H "Content-Type: application/json" \
  -d '{"x": 500, "y": 300, "hue": 170, "width": 4, "name": "Alice"}'

JavaScript fetch example

const res = await fetch('https://turlte.nl/api', {
  method:  'POST',
  headers: { 'Content-Type': 'application/json' },
  body:    JSON.stringify({
    x:     500,
    y:     300,
    hue:   170,   // blue
    width: 4,
    name:  'Alice',
  }),
});
const data = await res.json();
console.log(data);

Tier 3 — WebSocket full control

WS wss://turlte.nl/ws

Open a persistent WebSocket connection and send JSON commands. This unlocks Logo-style turtle movement: move forward by steps, turn by degrees, lift or lower the pen. A 👑 crown appears above your turtle on the canvas as long as the WebSocket connection is — or has ever been — active.

Connecting

const ws = new WebSocket('wss://turlte.nl/ws');

ws.onopen    = () => console.log('connected');
ws.onmessage = (e) => console.log(JSON.parse(e.data));
ws.onerror   = (e) => console.error(e);

On connection, the server immediately sends a hello message with your turtle's current state.

Sending commands

Every message must be valid JSON. After each message the server replies with an ack containing your updated turtle state.

Command reference

cmdExtra fieldsEffect
move steps (number) Move forward steps pixels in the current heading direction. Negative steps move backward.
turn degrees (number) Rotate the turtle. Positive = clockwise. 90 turns right, -90 turns left.
penup Lift the pen. Movement no longer draws a trail.
pendown Lower the pen. Movement draws a trail again.
coinspray Emit a burst of 🪙 coin particles from the turtle's position.

Any command can also include the POST-tier state fields (name, hue, width, x, y) — they are applied before the command is executed.

Heading convention

Heading follows the Logo convention: points up, 90° points right, 180° points down, 270° points left. Angles always increase clockwise.

Example session

const ws = new WebSocket('wss://turlte.nl/ws');

ws.onopen = () => {
  // Name the turtle and set a colour
  ws.send(JSON.stringify({ name: 'Bob', hue: 85, width: 3 }));

  // Draw a square
  for (let i = 0; i < 4; i++) {
    ws.send(JSON.stringify({ cmd: 'move',  steps:   100 }));
    ws.send(JSON.stringify({ cmd: 'turn',  degrees: 90  }));
  }

  // Celebrate
  ws.send(JSON.stringify({ cmd: 'coinspray' }));
};

ws.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  if (msg.type === 'ack') console.log('position:', msg.state.x, msg.state.y);
};
Note: WebSocket messages are processed one at a time on the server. If you send many messages in rapid succession, each one is queued and executed in order. Watch the canvas update live.

GET /api/turtles

GET https://turlte.nl/api/turtles

Returns all currently active turtles. Turtles that have been inactive for more than 5 minutes are removed automatically.

Query parameters (all optional)

ParameterDescriptionExample
id Comma-separated list of turtle IDs to include. Returns only those turtles. ?id=a3f8c2d1e4b9,bb12ef34ab56
tier Comma-separated list of tiers to include: get, post, ws. ?tier=post,ws

Examples

# All active turtles
curl https://turlte.nl/api/turtles

# Only turtles using WebSocket (have a crown)
curl "https://turlte.nl/api/turtles?tier=ws"

# A specific turtle by ID
curl "https://turlte.nl/api/turtles?id=a3f8c2d1e4b9"

Response

{
  "ok": true,
  "turtles": [
    {
      "id":      "a3f8c2d1e4b9",
      "name":    "Alice",
      "x":       500,
      "y":       300,
      "heading": 90,
      "hue":     170,
      "width":   4,
      "penDown": true,
      "tier":    "post"
    }
  ]
}

JavaScript example

// Poll the canvas every second and log all crown-turtles (WebSocket tier)
setInterval(async () => {
  const res  = await fetch('https://turlte.nl/api/turtles?tier=ws');
  const data = await res.json();
  console.log(data.turtles);
}, 1000);

GET /api/history

GET https://turlte.nl/api/history

Returns the complete stored history: every trail segment ever drawn, every turtle state snapshot, and the timestamp range. This is the data used by the canvas timeline slider.

This response can be large in long-running sessions. Prefer /api/turtles when you only need current positions.

Response

{
  "segments": [
    {
      "id":         1,
      "ip":         "a3f8c2d1e4b9",  // turtle ID
      "x0": 200, "y0": 300,
      "x1": 350, "y1": 420,
      "hue":        85,
      "width":      3,
      "created_at": 1712345678000  // Unix ms timestamp
    }
  ],
  "snapshots": [
    {
      "id":         1,
      "ip":         "a3f8c2d1e4b9",
      "name":       "Alice",
      "x": 350, "y": 420, "heading": 90,
      "hue": 85, "width": 3, "tier": "post",
      "created_at": 1712345678000
    }
  ],
  "range": {
    "min": 1712345000000,
    "max": 1712345999000
  }
}

Turtle state reference

All API responses include a state object:

FieldTypeDescription
id string Your anonymous 12-character identifier (hashed from your IP — never exposed raw)
name string Display name shown on the canvas (default: your id)
x number Current x position (0 – 1000)
y number Current y position (0 – 600)
heading number Direction in degrees (0 = up, clockwise)
hue integer Trail colour hue (0 – 255)
width integer Trail stroke width (1 – 16 px)
penDown boolean Whether movement draws a trail
tier string "get" / "post" / "ws" — highest tier used so far

Errors

All errors return HTTP 400 with a JSON body:

{
  "ok":     false,
  "errors": [
    { "msg": "x is required and must be a number between 0 and 1000" }
  ]
}

Common causes:

WebSocket errors are returned as a message to the sending client:

{ "type": "error", "message": "Unrecognized message. Valid cmds: move, turn, penup, pendown, coinspray" }