← Realm Atlas / API
Tokens

Drive Realm Atlas from your own code

Everything the page does is available over HTTP. A natural use is a world kept in a file - the JSON the page exports, or a setting bible from another tool - where a script asks for lore on the regions that changed, or for a trade reading of the whole map, whenever the geography moves on.

One thing to be clear about first: the map's coordinates are never sent. The caller measures the map and sends only the gazetteer it computed - which regions border which and for how many km, which have a coast, where each river rises and ends, which place stands in which region, which roads join what - plus the map's own findings. The model writes against those facts and must state every geographic claim explicitly, so the caller can check each one before showing it. The page's own measurer and checker ship as plain scripts you can load in node — see the end of the examples.

Base URL and the envelope

Every endpoint lives under https://api.skillsafe.ai/v1/app-api, and every response uses one envelope:

{ "ok": true,  "data":  { ... } }
{ "ok": false, "error": { "code": "...", "message": "...", "status": 402, "details": { ... } } }

The token is minted for this app (the guest call takes {"slug":"realm-atlas"}), so no slug header is needed afterwards. Send it as Authorization: Bearer …. The run body is the input object: never wrap it as {"input": {…}}. The app declares an input schema, so that body still returns 200 and is billed, but /estimate warns unknown field 'input' and the model sees none of your fields — a paid mistake.

Errors:

StatusCodeMeaning
400validation_errorThe body is not a JSON object.
401unauthorizedNo token, or a stale one. Mint a guest token or sign in again.
402insufficient_creditsThe balance is under min_credits. Price with /estimate first.
403forbiddenA guest token tried to run: lore and trade runs are metered and need a personal token.
404not_foundUnknown job id.
429rate_limitedBack off and retry.
5xxserver_errorTransient. Retry with the same Idempotency-Key so a retry never double-bills.

The task field comes first

One system prompt serves two lanes. task picks the contract; anything missing or unknown is read as lore and the reply says so in notes.

taskWhat it doesExtra fields
loreOne gazetteer entry for each region in focus, in that order: people, livelihood, trade, a quarrel with a neighbour, a landmark. Grounded in the worldbuilding skill.focus; on an update run also previous and changes, and the reply adds one updated item per focus region.
tradeReads the whole map as an economy - resources per region, the main flows of goods, the pressures - and proposes 2-6 edits: roads, sea lanes, ports and border disputes. Grounded in the economic-systems skill.None.

Every run body is a flat JSON object whose every value is a string; the structured fields hold JSON encoded as a string. The declared input schema lists exactly the fields below; task, title and regions are required. The page builds it with Atlas.buildInput in atlas.js.

FieldWhat it holds
task"lore" or "trade".
titleThe world's name.
seedThe person's notes on tone and history (the model treats them as information, not instructions). Past 3,000 characters the page cuts the middle and marks the cut.
scaleUnits, e.g. "1 map unit = 2 km; areas in km2, lengths in km".
regionsJSON string: [{"id":"R1","name","terrain","climate","area_km2","coastal","coast_km","runs_off_map"?,"neighbours":[{"id","border_km"}],"lakes"?,"lakeshore_km"?,"rivers":[{"id","role"}],"places":[ids],"roads":[ids],"disputes"?,"note"?}]. A lake is {"id","name","terrain":"lake","climate","area_km2","lake":true}. Past 36 regions, the focus regions and their neighbours go in full and the rest as {"id","name","terrain","climate","coastal"}.
placesJSON string: [{"id":"P1","name","kind":"city|town|port|fort|mine|temple|ruin|landmark","region","coastal"?,"lakeside"?,"river"?,"at_sea"?,"roads"?,"note"?}]. At most 90; past that, cities, ports and places in focus regions go first.
riversJSON string: [{"id":"V1","name","source","mouth_region","ends":"sea|lake|river|inland","into","through":[region ids],"lakes","length_km"}], drawn source to mouth.
roadsJSON string: [{"id":"D1","name","kind":"road|trade|sea","connects":[place ids],"through":[region ids],"crosses":[river ids],"length_km"}]. "sea" is a sea lane.
checksJSON string: {"findings":[{"key","sev","msg"}],"disputes":[{"id":"X1","a","b","note"}],"totals":{...}}. Findings are problems on the map (a river running uphill, a gap); at most 30 are sent.
focuslore only. JSON string: an array of up to 6 land region ids, e.g. "[\"R3\",\"R6\"]".
previouslore update runs only. JSON string: the earlier entries for the focus regions, [{"region","epithet","text","trade","tension","landmark"}].
changeslore update runs only. JSON string: per region, what changed on the map since, e.g. {"R7":["new place P11"]}.
clippedOptional JSON string, sent only when something was left out: {"regions_total","regions_detailed","places_total","places_sent","findings_total","findings_sent","seed_chars_cut","focus_dropped"}.
retry_noteOptional. The page sets it on its one reformat retry (attempt 2) when a reply was not a single valid JSON object; leave it out.

You can build these strings yourself from your own data - a minimal lore body looks like this (each structured value is JSON.stringify of an array or object) - or open the world on the page, use Export → JSON, and run Atlas.buildInput on it as shown below:

{
 "task": "lore",
 "title": "The Two Shores",
 "seed": "Two river kingdoms that share one ford.",
 "scale": "1 map unit = 1 km; areas in km2, lengths in km",
 "regions": "[{\"id\":\"R1\",\"name\":\"Amber Vale\",\"terrain\":\"plains\",\"climate\":\"temperate\",\"area_km2\":9000,\"coastal\":false,\"coast_km\":0,\"neighbours\":[{\"id\":\"R2\",\"border_km\":80}],\"rivers\":[{\"id\":\"V1\",\"role\":\"through\"}],\"places\":[\"P1\"],\"roads\":[]}, ...]",
 "places": "[{\"id\":\"P1\",\"name\":\"Ambergate\",\"kind\":\"town\",\"region\":\"R1\",\"river\":\"V1\"}]",
 "rivers": "[{\"id\":\"V1\",\"name\":\"Sill\",\"source\":\"R2\",\"mouth_region\":\"R1\",\"ends\":\"inland\",\"into\":null,\"through\":[\"R2\",\"R1\"],\"lakes\":[],\"length_km\":140}]",
 "roads": "[]",
 "checks": "{\"findings\":[],\"disputes\":[],\"totals\":{}}",
 "focus": "[\"R1\"]"
}

The reply contracts

The reply text is ONE JSON object, no prose or code fence, with every key of the lane's shape present ([] or null when empty). Map features are referred to by id - R.. regions, P.. places, V.. rivers, D.. roads, X.. disputes - and every geographic statement carries a claims list the caller can check:

{"type": "adjacent", "a": "R1", "b": "R2"}
{"type": "coastal",  "region": "R1", "value": true}
{"type": "climate",  "region": "R1", "value": "oceanic"}
{"type": "terrain",  "region": "R1", "value": "hills"}
{"type": "river",    "river": "V1", "region": "R6"}
{"type": "place",    "place": "P5", "region": "R6"}
{"type": "road",     "road": "D1", "place": "P5"}
{"type": "lake",     "region": "R2", "lake": "R3"}

lore

{
  "task": "lore", "title": "...", "headline": "one sentence on what ties these regions together",
  "entries": [{
    "region": "R6", "epithet": "a short byname",
    "sentences": [{ "topic": "people|livelihood|trade|tension|landmark|history|belief", "text", "refs": ["R6", "V1"], "claims": [...] }],
    "trade":    [{ "with": "R8", "exports": [...], "imports": [...], "route": "D1|V1|border|sea" }],
    "tension":  { "with": "R2", "over": "one sentence" } or null,
    "landmark": { "place": "P5", "note": "one sentence" } or null
  }],
  "updated": [{ "region": "R4", "change": "what changed on the map", "kept": "what was kept" }],
  "notes": [...]
}

One entry per focus region, in order, and none for any other region. Each entry has 5-7 sentences covering at least people, livelihood, trade, tension and landmark; 1-3 trade partners, where "border" is only between neighbours and "sea" only between two coastal regions; a tension with exactly one neighbour; a landmark from the region's own places. updated is [] on a first run.

trade

{
  "task": "trade", "title": "...", "headline": "how this world's wealth moves",
  "currency": "one sentence on money or the medium of exchange",
  "resources": [{ "region": "R2", "goods": [...], "basis": "why, from terrain, climate or a place" }],
  "flows":     [{ "from": "R2", "to": "R6", "goods": [...], "route": "D1|V1|border|sea|proposal:1", "note" }],
  "pressures": [{ "text", "refs": [...], "claims": [...] }],
  "proposals": [{ "n": 1, "action": "add_road|add_sea_lane|add_port|add_dispute",
                  "from", "to", "region", "river", "other", "name", "kind": "road|trade" or null, "reason", "claims": [...] }],
  "notes": [...]
}

One resource line per land region; 3-10 flows; 2-4 pressures; 2-6 proposals, most valuable first. add_road joins two places in the same or neighbouring regions that no road joins yet; add_sea_lane two places on the water; add_port names a coastal region and optionally a river that reaches the sea there; add_dispute two neighbours in region and other. Fields an action does not use are null. No reply ever carries coordinates: the page places everything from ids.

Worked examples

These are the bundled examples on the page: the exact run body the page builds and a reply produced against the shipped prompt (the page replays the same replies for free).

lore: four regions of a cool northern continent

Input for The Vellmark Reach, eight regions, with focus on Ostval, Tessaly Coast, the Kael Basin and Duskmere Downs. The map carries two warnings about the Merrow, which the reply keeps out of the lore and names in notes:

{
 "task": "lore",
 "title": "The Vellmark Reach",
 "seed": "A cool northern continent a generation after the Sundering War. The old crown sits at Caer Hald, but the grain and the river trade of the Kael Basin decide who really rules. Sarn Heights holds the iron. The south is warm, wine-growing and suspicious of the north. Tone: low magic, politics and trade.",
 "scale": "1 map unit = 2 km; areas in km2, lengths in km",
 "regions": "(a JSON string - decoded below)",
 "places": "(a JSON string - decoded below)",
 "rivers": "(a JSON string - decoded below)",
 "roads": "(a JSON string - decoded below)",
 "checks": "(a JSON string - decoded below)",
 "focus": "[\"R3\",\"R4\",\"R6\",\"R7\"]"
}

regions, decoded:

[
 {
  "id": "R1",
  "name": "Haldmoor",
  "terrain": "hills",
  "climate": "oceanic",
  "area_km2": 103000,
  "coastal": true,
  "coast_km": 576,
  "neighbours": [
   {
    "id": "R2",
    "border_km": 360
   },
   {
    "id": "R5",
    "border_km": 400
   }
  ],
  "rivers": [
   {
    "id": "V3",
    "role": "end"
   }
  ],
  "places": [
   "P1"
  ],
  "roads": [
   "D1"
  ]
 },
 {
  "id": "R2",
  "name": "Sarn Heights",
  "terrain": "mountains",
  "climate": "highland",
  "area_km2": 121600,
  "coastal": true,
  "coast_km": 382,
  "neighbours": [
   {
    "id": "R1",
    "border_km": 360
   },
   {
    "id": "R3",
    "border_km": 280
   },
   {
    "id": "R6",
    "border_km": 382
   }
  ],
  "rivers": [
   {
    "id": "V1",
    "role": "source"
   }
  ],
  "places": [
   "P2",
   "P6"
  ],
  "roads": []
 },
 {
  "id": "R3",
  "name": "Ostval",
  "terrain": "forest",
  "climate": "subarctic",
  "area_km2": 163800,
  "coastal": true,
  "coast_km": 675,
  "neighbours": [
   {
    "id": "R2",
    "border_km": 280
   },
   {
    "id": "R4",
    "border_km": 372
   },
   {
    "id": "R6",
    "border_km": 342
   }
  ],
  "rivers": [
   {
    "id": "V2",
    "role": "source"
   }
  ],
  "places": [
   "P3"
  ],
  "roads": []
 },
 {
  "id": "R4",
  "name": "Tessaly Coast",
  "terrain": "plains",
  "climate": "temperate",
  "area_km2": 170800,
  "coastal": true,
  "coast_km": 596,
  "neighbours": [
   {
    "id": "R3",
    "border_km": 372
   },
   {
    "id": "R6",
    "border_km": 341
   },
   {
    "id": "R7",
    "border_km": 320
   }
  ],
  "rivers": [
   {
    "id": "V2",
    "role": "mouth"
   }
  ],
  "places": [
   "P4"
  ],
  "roads": []
 },
 {
  "id": "R5",
  "name": "Merrow Fen",
  "terrain": "marsh",
  "climate": "oceanic",
  "area_km2": 139200,
  "coastal": true,
  "coast_km": 322,
  "neighbours": [
   {
    "id": "R1",
    "border_km": 400
   },
   {
    "id": "R6",
    "border_km": 340
   },
   {
    "id": "R8",
    "border_km": 442
   }
  ],
  "rivers": [
   {
    "id": "V3",
    "role": "source"
   }
  ],
  "places": [
   "P9"
  ],
  "roads": [
   "D1"
  ]
 },
 {
  "id": "R6",
  "name": "Kael Basin",
  "terrain": "steppe",
  "climate": "semi-arid",
  "area_km2": 262600,
  "coastal": false,
  "coast_km": 0,
  "neighbours": [
   {
    "id": "R2",
    "border_km": 382
   },
   {
    "id": "R3",
    "border_km": 342
   },
   {
    "id": "R4",
    "border_km": 341
   },
   {
    "id": "R5",
    "border_km": 340
   },
   {
    "id": "R7",
    "border_km": 360
   },
   {
    "id": "R8",
    "border_km": 380
   }
  ],
  "rivers": [
   {
    "id": "V1",
    "role": "through"
   }
  ],
  "places": [
   "P5",
   "P10"
  ],
  "roads": [
   "D1",
   "D2"
  ]
 },
 {
  "id": "R7",
  "name": "Duskmere Downs",
  "terrain": "hills",
  "climate": "mediterranean",
  "area_km2": 168800,
  "coastal": true,
  "coast_km": 686,
  "neighbours": [
   {
    "id": "R4",
    "border_km": 320
   },
   {
    "id": "R6",
    "border_km": 360
   },
   {
    "id": "R
... (shortened here)

places, decoded:

[
 {
  "id": "P1",
  "name": "Caer Hald",
  "kind": "city",
  "region": "R1",
  "roads": [
   "D1"
  ],
  "note": "the old royal seat"
 },
 {
  "id": "P2",
  "name": "Sarnhold",
  "kind": "fort",
  "region": "R2"
 },
 {
  "id": "P3",
  "name": "Ostgate",
  "kind": "port",
  "region": "R3",
  "coastal": true
 },
 {
  "id": "P4",
  "name": "Tessmouth",
  "kind": "port",
  "region": "R4",
  "coastal": true,
  "river": "V2"
 },
 {
  "id": "P5",
  "name": "Kaelford",
  "kind": "town",
  "region": "R6",
  "river": "V1",
  "roads": [
   "D1"
  ]
 },
 {
  "id": "P6",
  "name": "Iron Gap",
  "kind": "mine",
  "region": "R2"
 },
 {
  "id": "P7",
  "name": "Duskwater",
  "kind": "city",
  "region": "R7",
  "roads": [
   "D2"
  ]
 },
 {
  "id": "P8",
  "name": "Lowhaven",
  "kind": "port",
  "region": "R8",
  "coastal": true
 },
 {
  "id": "P9",
  "name": "Ruins of Merrow",
  "kind": "ruin",
  "region": "R5"
 },
 {
  "id": "P10",
  "name": "Kaelmoot",
  "kind": "city",
  "region": "R6",
  "roads": [
   "D1",
   "D2"
  ],
  "note": "seat of the grain moot"
 }
]

rivers, decoded:

[
 {
  "id": "V1",
  "name": "Kael",
  "source": "R2",
  "mouth_region": "R8",
  "ends": "sea",
  "into": null,
  "through": [
   "R2",
   "R6",
   "R8"
  ],
  "lakes": [],
  "length_km": 912
 },
 {
  "id": "V2",
  "name": "Tess",
  "source": "R3",
  "mouth_region": "R4",
  "ends": "sea",
  "into": null,
  "through": [
   "R3",
   "R4"
  ],
  "lakes": [],
  "length_km": 560
 },
 {
  "id": "V3",
  "name": "Merrow",
  "source": "R5",
  "mouth_region": "R1",
  "ends": "inland",
  "into": null,
  "through": [
   "R5",
   "R1"
  ],
  "lakes": [],
  "length_km": 342
 }
]

roads, decoded:

[
 {
  "id": "D1",
  "name": "King's Road",
  "kind": "road",
  "connects": [
   "P1",
   "P5",
   "P10"
  ],
  "through": [
   "R1",
   "R5",
   "R6"
  ],
  "crosses": [
   "V1"
  ],
  "length_km": 756
 },
 {
  "id": "D2",
  "name": "Southway",
  "kind": "trade",
  "connects": [
   "P7",
   "P10"
  ],
  "through": [
   "R6",
   "R7"
  ],
  "crosses": [],
  "length_km": 472
 }
]

checks, decoded:

{
 "findings": [
  {
   "key": "river_mouth:V3",
   "sev": "warn",
   "msg": "The Merrow ends inland in Haldmoor. Run it on to the coast, into a lake, or onto another river."
  },
  {
   "key": "river_uphill:V3:R5>R1",
   "sev": "warn",
   "msg": "The Merrow runs uphill from Merrow Fen (marsh) into Haldmoor (hills). Rivers fall from higher ground; reverse it or retag the terrain."
  }
 ],
 "disputes": [],
 "totals": {
  "regions": 8,
  "lakes": 0,
  "coastal": 7,
  "land_km2": 1447400,
  "places": 10,
  "rivers": 3,
  "roads": 2,
  "disputes": 0,
  "river_km": 1814,
  "road_km": 1228
 }
}

Reply (trimmed to 1 of 4 entries; the real reply has every item and the page shows it whole):

{
 "task": "lore",
 "title": "The Vellmark Reach",
 "headline": "Ostval, Tessaly Coast, Kael Basin and Duskmere Downs all ring the grain moot at Kaelmoot, and each one's fortune depends on how cheaply it can reach the Kael Basin's harvest.",
 "entries": [
  {
   "region": "R3",
   "epithet": "the timber march of the cold north",
   "sentences": [
    {
     "topic": "people",
     "text": "Ostval is subarctic forest, and its Ostmen live in steep-roofed lodges of spruce, counting their years by hard winters and their debts in cords of wood.",
     "refs": [
      "R3"
     ],
     "claims": [
      {
       "type": "terrain",
       "region": "R3",
       "value": "forest"
      },
      {
       "type": "climate",
       "region": "R3",
       "value": "subarctic"
      }
     ]
    },
    {
     "topic": "livelihood",
     "text": "Rye and grain will not ripen under the Ostval pines, so families fell masts, render pitch and trap marten and fox for furs through the long dark.",
     "refs": [
      "R3"
     ],
     "claims": []
    },
    {
     "topic": "trade",
     "text": "The Tess rises in Ostval, and each thaw the lumber guilds float rafts of logs down it to the plains of Tessaly Coast.",
     "refs": [
      "V2",
      "R3",
      "R4"
     ],
     "claims": [
      {
       "type": "river",
       "river": "V2",
       "region": "R3"
      },
      {
       "type": "river",
       "river": "V2",
       "region": "R4"
      },
      {
       "type": "adjacent",
       "a": "R3",
       "b": "R4"
      }
     ]
    },
    {
     "topic": "landmark",
     "text": "Ostgate, the only port of Ostval, ships pitch and furs along the coast and takes in the salt and grain the forest cannot make.",
     "refs": [
      "P3",
      "R3"
     ],
     "claims": [
      {
       "type": "place",
       "place": "P3",
       "region": "R3"
      },
      {
       "type": "coastal",
       "region": "R3",
       "value": true
      }
     ]
    },
    {
     "topic": "tension",
     "text": "No road joins Ostval to the Kael Basin, so Ostmen pay whatever the Kaelmoot grain factors ask, and every lean spring they curse the moot by name.",
     "refs": [
      "R3",
      "R6",
      "P10"
     ],
     "claims": [
      {
       "type": "adjacent",
       "a": "R3",
       "b": "R6"
      },
      {
       "type": "place",
       "place": "P10",
       "region": "R6"
      }
     ]
    },
    {
     "topic": "history",
     "text": "Ostval sent axemen to both sides in the Sundering War, and old families still hang the stump of a felled pine over the door of a house that chose wrong.",
     "refs": [
      "R3"
     ],
     "claims": []
    },
    {
     "topic": "belief",
     "text": "Ostmen leave the first log of every raft standing upright on the Tess bank as a gift to the river, which they call the Grey Mother.",
     "refs": [
      "R3",
      "V2"
     ],
     "claims": [
      {
       "type": "river",
       "river": "V2",
       "region": "R3"
      }
     ]
    }
   ],
   "trade": [
    {
     "with": "R4",
     "exports": [
      "timber",
      "pitch"
     ],
     "imports": [
      "salt",
      "cloth"
     ],
     "route": "V2"
    },
    {
     "with": "R6",
     "exports": [
      "furs",
      "charcoal"
     ],
     "imports": [
      "grain",
      "horses"
     ],
     "route": "border"
    }
   ],
   "tension": {
    "with": "R6",
    "over": "Kaelmoot sets the grain price Ostval must pay, and the forest has nothing the moot cannot buy elsewhere."
   },
   "landmark": {
    "place": "P3",
    "note": "Ostgate is Ostval's only harbour and the one door out of the forest that the grain factors do not hold."
   }
  }
 ],
 "updated": [],
 "notes": [
  "The map flags the Merrow: it ends inland in Haldmoor and runs uphill from Merrow Fen into the hills. No lore here leans on it; reverse it or run it to the coast before writing about Haldmoor or Merrow Fen."
 ]
}

trade: a chain of windy islands

Input for The Saltreach Isles: six coastal regions, eight places, one river, a road and a sea lane, and a clean map. A trade input has no focus:

{
 "task": "trade",
 "title": "The Saltreach Isles",
 "seed": "A chain of windy islands in a cold grey sea. Every island is half fishing village and half fortress. Skarr has the only metal. The islanders count wealth in boats. A school project: show how islands depend on each other.",
 "scale": "1 map unit = 1.5 km; areas in km2, lengths in km",
 "regions": "(a JSON string - decoded below)",
 "places": "(a JSON string - decoded below)",
 "rivers": "(a JSON string - decoded below)",
 "roads": "(a JSON string - decoded below)",
 "checks": "(a JSON string - decoded below)"
}

regions, decoded:

[
 {
  "id": "R1",
  "name": "Grenhold West",
  "terrain": "forest",
  "climate": "oceanic",
  "area_km2": 44325,
  "coastal": true,
  "coast_km": 557,
  "neighbours": [
   {
    "id": "R2",
    "border_km": 379
   }
  ],
  "rivers": [],
  "places": [
   "P2"
  ],
  "roads": []
 },
 {
  "id": "R2",
  "name": "Grenhold East",
  "terrain": "hills",
  "climate": "oceanic",
  "area_km2": 48038,
  "coastal": true,
  "coast_km": 578,
  "neighbours": [
   {
    "id": "R1",
    "border_km": 379
   }
  ],
  "rivers": [],
  "places": [
   "P1",
   "P7"
  ],
  "roads": [
   "D2"
  ]
 },
 {
  "id": "R3",
  "name": "Tiriel",
  "terrain": "plains",
  "climate": "oceanic",
  "area_km2": 47925,
  "coastal": true,
  "coast_km": 837,
  "neighbours": [],
  "rivers": [],
  "places": [
   "P3"
  ],
  "roads": [
   "D2"
  ]
 },
 {
  "id": "R4",
  "name": "Skarr",
  "terrain": "mountains",
  "climate": "subarctic",
  "area_km2": 32963,
  "coastal": true,
  "coast_km": 721,
  "neighbours": [],
  "rivers": [],
  "places": [
   "P4"
  ],
  "roads": []
 },
 {
  "id": "R5",
  "name": "Corran",
  "terrain": "plains",
  "climate": "temperate",
  "area_km2": 75375,
  "coastal": true,
  "coast_km": 1026,
  "neighbours": [],
  "rivers": [
   {
    "id": "V1",
    "role": "source"
   }
  ],
  "places": [
   "P5",
   "P8"
  ],
  "roads": [
   "D1"
  ]
 },
 {
  "id": "R6",
  "name": "Little Mewl",
  "terrain": "marsh",
  "climate": "oceanic",
  "area_km2": 10913,
  "coastal": true,
  "coast_km": 419,
  "neighbours": [],
  "rivers": [],
  "places": [
   "P6"
  ],
  "roads": []
 }
]

places, decoded:

[
 {
  "id": "P1",
  "name": "Grenport",
  "kind": "port",
  "region": "R2",
  "coastal": true,
  "roads": [
   "D2"
  ]
 },
 {
  "id": "P2",
  "name": "Wester",
  "kind": "town",
  "region": "R1"
 },
 {
  "id": "P3",
  "name": "Tiriel Harbour",
  "kind": "port",
  "region": "R3",
  "coastal": true,
  "roads": [
   "D2"
  ]
 },
 {
  "id": "P4",
  "name": "Skarr Mines",
  "kind": "mine",
  "region": "R4"
 },
 {
  "id": "P5",
  "name": "Corran Town",
  "kind": "city",
  "region": "R5",
  "roads": [
   "D1"
  ]
 },
 {
  "id": "P6",
  "name": "Mewl Light",
  "kind": "landmark",
  "region": "R6",
  "note": "a lighthouse on the reef"
 },
 {
  "id": "P7",
  "name": "Old Watch",
  "kind": "ruin",
  "region": "R2",
  "coastal": true
 },
 {
  "id": "P8",
  "name": "Corran Quay",
  "kind": "port",
  "region": "R5",
  "coastal": true,
  "roads": [
   "D1"
  ]
 }
]

rivers, decoded:

[
 {
  "id": "V1",
  "name": "Corr",
  "source": "R5",
  "mouth_region": "R5",
  "ends": "sea",
  "into": null,
  "through": [
   "R5"
  ],
  "lakes": [],
  "length_km": 234
 }
]

roads, decoded:

[
 {
  "id": "D1",
  "name": "Quay Road",
  "kind": "road",
  "connects": [
   "P5",
   "P8"
  ],
  "through": [
   "R5"
  ],
  "crosses": [],
  "length_km": 137
 },
 {
  "id": "D2",
  "name": "Grey Crossing",
  "kind": "sea",
  "connects": [
   "P1",
   "P3"
  ],
  "through": [
   "R2",
   "R3"
  ],
  "crosses": [],
  "length_km": 228
 }
]

checks, decoded:

{
 "findings": [],
 "disputes": [],
 "totals": {
  "regions": 6,
  "lakes": 0,
  "coastal": 6,
  "land_km2": 259539,
  "places": 8,
  "rivers": 1,
  "roads": 2,
  "disputes": 0,
  "river_km": 234,
  "road_km": 365
 }
}

Reply (trimmed to 2 of 6 resources, 2 of 7 flows, 1 of 3 pressures, 2 of 5 proposals; the real reply has every item and the page shows it whole):

{
 "task": "trade",
 "title": "The Saltreach Isles",
 "headline": "Every island has a coast but only one sea lane joins any two of them, so most goods still cross the grey water in single boats at the mercy of the weather. Corran feeds the chain, Skarr arms it, and Grenhold sits between them hoping to take a toll.",
 "currency": "Islanders count wealth in boats: a debt is reckoned in boat-shares, so a man may owe a third of a herring smack, and Skarr iron nails serve as small change.",
 "resources": [
  {
   "region": "R1",
   "goods": [
    "timber",
    "tar",
    "boat planks"
   ],
   "basis": "Oceanic forest, the only woodland in the chain, around the town of Wester."
  },
  {
   "region": "R2",
   "goods": [
    "sheep",
    "wool",
    "herring"
   ],
   "basis": "Oceanic hills with Grenport, the end of the Grey Crossing."
  }
 ],
 "flows": [
  {
   "from": "R4",
   "to": "R5",
   "goods": [
    "iron"
   ],
   "route": "sea",
   "note": "Skarr ore goes to the smiths of Corran Town in open boats."
  },
  {
   "from": "R5",
   "to": "R4",
   "goods": [
    "wheat",
    "ale"
   ],
   "route": "sea",
   "note": "Skarr cannot grow grain on its peaks and must buy every loaf."
  }
 ],
 "pressures": [
  {
   "text": "Skarr Mines hold the only metal in the Saltreach Isles, yet Skarr has no neighbour and no harbour, so every nail and anchor waits on a calm day.",
   "refs": [
    "P4",
    "R4"
   ],
   "claims": [
    {
     "type": "place",
     "place": "P4",
     "region": "R4"
    },
    {
     "type": "coastal",
     "region": "R4",
     "value": true
    }
   ]
  }
 ],
 "proposals": [
  {
   "n": 1,
   "action": "add_port",
   "from": null,
   "to": null,
   "region": "R4",
   "river": null,
   "other": null,
   "name": "Skarr Landing",
   "kind": null,
   "reason": "Skarr is coastal and holds the only mine in the isles, but has no harbour; a landing would let iron leave without hauling ore to an open beach.",
   "claims": [
    {
     "type": "coastal",
     "region": "R4",
     "value": true
    },
    {
     "type": "place",
     "place": "P4",
     "region": "R4"
    }
   ]
  },
  {
   "n": 2,
   "action": "add_sea_lane",
   "from": "P8",
   "to": "P1",
   "region": null,
   "river": null,
   "other": null,
   "name": "Corn Lane",
   "kind": null,
   "reason": "Corran Quay and Grenport are both ports with no lane between them; Corran's grain would reach Grenhold on a steady run instead of chance sailings.",
   "claims": [
    {
     "type": "place",
     "place": "P8",
     "region": "R5"
    },
    {
     "type": "place",
     "place": "P1",
     "region": "R2"
    }
   ]
  }
 ],
 "notes": []
}

lore update: the continent after it was redrawn

The same continent after two shared corners were dragged east and the town of Marrowby was founded. The four regions whose facts changed are the focus; previous holds their earlier entries and changes what moved on the map. The reply adds one updated item per region:

{
 "task": "lore",
 "title": "The Vellmark Reach",
 "seed": "A cool northern continent a generation after the Sundering War. The old crown sits at Caer Hald, but the grain and the river trade of the Kael Basin decide who really rules. Sarn Heights holds the iron. The south is warm, wine-growing and suspicious of the north. Tone: low magic, politics and trade.",
 "scale": "1 map unit = 2 km; areas in km2, lengths in km",
 "regions": "(a JSON string - decoded below)",
 "places": "(a JSON string - decoded below)",
 "rivers": "(a JSON string - decoded below)",
 "roads": "(a JSON string - decoded below)",
 "checks": "(a JSON string - decoded below)",
 "focus": "[\"R3\",\"R4\",\"R6\",\"R7\"]",
 "previous": "(a JSON string - decoded below)",
 "changes": "(a JSON string - decoded below)"
}

regions, decoded:

[
 {
  "id": "R1",
  "name": "Haldmoor",
  "terrain": "hills",
  "climate": "oceanic",
  "area_km2": 103000,
  "coastal": true,
  "coast_km": 576,
  "neighbours": [
   {
    "id": "R2",
    "border_km": 360
   },
   {
    "id": "R5",
    "border_km": 400
   }
  ],
  "rivers": [
   {
    "id": "V3",
    "role": "end"
   }
  ],
  "places": [
   "P1"
  ],
  "roads": [
   "D1"
  ]
 },
 {
  "id": "R2",
  "name": "Sarn Heights",
  "terrain": "mountains",
  "climate": "highland",
  "area_km2": 121600,
  "coastal": true,
  "coast_km": 382,
  "neighbours": [
   {
    "id": "R1",
    "border_km": 360
   },
   {
    "id": "R3",
    "border_km": 280
   },
   {
    "id": "R6",
    "border_km": 382
   }
  ],
  "rivers": [
   {
    "id": "V1",
    "role": "source"
   }
  ],
  "places": [
   "P2",
   "P6"
  ],
  "roads": []
 },
 {
  "id": "R3",
  "name": "Ostval",
  "terrain": "forest",
  "climate": "subarctic",
  "area_km2": 210200,
  "coastal": true,
  "coast_km": 675,
  "neighbours": [
   {
    "id": "R2",
    "border_km": 280
   },
   {
    "id": "R4",
    "border_km": 349
   },
   {
    "id": "R6",
    "border_km": 519
   }
  ],
  "rivers": [
   {
    "id": "V2",
    "role": "source"
   }
  ],
  "places": [
   "P3"
  ],
  "roads": []
 },
 {
  "id": "R4",
  "name": "Tessaly Coast",
  "terrain": "plains",
  "climate": "temperate",
  "area_km2": 94000,
  "coastal": true,
  "coast_km": 596,
  "neighbours": [
   {
    "id": "R3",
    "border_km": 349
   },
   {
    "id": "R6",
    "border_km": 283
   },
   {
    "id": "R7",
    "border_km": 146
   }
  ],
  "rivers": [
   {
    "id": "V2",
    "role": "mouth"
   }
  ],
  "places": [
   "P4"
  ],
  "roads": []
 },
 {
  "id": "R5",
  "name": "Merrow Fen",
  "terrain": "marsh",
  "climate": "oceanic",
  "area_km2": 139200,
  "coastal": true,
  "coast_km": 322,
  "neighbours": [
   {
    "id": "R1",
    "border_km": 400
   },
   {
    "id": "R6",
    "border_km": 340
   },
   {
    "id": "R8",
    "border_km": 442
   }
  ],
  "rivers": [
   {
    "id": "V3",
    "role": "source"
   }
  ],
  "places": [
   "P9"
  ],
  "roads": [
   "D1"
  ]
 },
 {
  "id": "R6",
  "name": "Kael Basin",
  "terrain": "steppe",
  "climate": "semi-arid",
  "area_km2": 306600,
  "coastal": false,
  "coast_km": 0,
  "neighbours": [
   {
    "id": "R2",
    "border_km": 382
   },
   {
    "id": "R3",
    "border_km": 519
   },
   {
    "id": "R4",
    "border_km": 283
   },
   {
    "id": "R5",
    "border_km": 340
   },
   {
    "id": "R7",
    "border_km": 541
   },
   {
    "id": "R8",
    "border_km": 380
   }
  ],
  "rivers": [
   {
    "id": "V1",
    "role": "through"
   },
   {
    "id": "V2",
    "role": "through"
   }
  ],
  "places": [
   "P5",
   "P10"
  ],
  "roads": [
   "D1",
   "D2"
  ]
 },
 {
  "id": "R7",
  "name": "Duskmere Downs",
  "terrain": "hills",
  "climate": "mediterranean",
  "area_km2": 155200,
  "coastal": true,
  "coast_km": 686,
  "neighbours": [
   {
    "id": "R4",
    "border_km": 146
   },
   {
    "id": "R
... (shortened here)

places, decoded:

[
 {
  "id": "P1",
  "name": "Caer Hald",
  "kind": "city",
  "region": "R1",
  "roads": [
   "D1"
  ],
  "note": "the old royal seat"
 },
 {
  "id": "P2",
  "name": "Sarnhold",
  "kind": "fort",
  "region": "R2"
 },
 {
  "id": "P3",
  "name": "Ostgate",
  "kind": "port",
  "region": "R3",
  "coastal": true
 },
 {
  "id": "P4",
  "name": "Tessmouth",
  "kind": "port",
  "region": "R4",
  "coastal": true,
  "river": "V2"
 },
 {
  "id": "P5",
  "name": "Kaelford",
  "kind": "town",
  "region": "R6",
  "river": "V1",
  "roads": [
   "D1"
  ]
 },
 {
  "id": "P6",
  "name": "Iron Gap",
  "kind": "mine",
  "region": "R2"
 },
 {
  "id": "P7",
  "name": "Duskwater",
  "kind": "city",
  "region": "R7",
  "roads": [
   "D2"
  ]
 },
 {
  "id": "P8",
  "name": "Lowhaven",
  "kind": "port",
  "region": "R8",
  "coastal": true
 },
 {
  "id": "P9",
  "name": "Ruins of Merrow",
  "kind": "ruin",
  "region": "R5"
 },
 {
  "id": "P10",
  "name": "Kaelmoot",
  "kind": "city",
  "region": "R6",
  "roads": [
   "D1",
   "D2"
  ],
  "note": "seat of the grain moot"
 },
 {
  "id": "P11",
  "name": "Marrowby",
  "kind": "town",
  "region": "R7"
 }
]

rivers, decoded:

[
 {
  "id": "V1",
  "name": "Kael",
  "source": "R2",
  "mouth_region": "R8",
  "ends": "sea",
  "into": null,
  "through": [
   "R2",
   "R6",
   "R8"
  ],
  "lakes": [],
  "length_km": 912
 },
 {
  "id": "V2",
  "name": "Tess",
  "source": "R3",
  "mouth_region": "R4",
  "ends": "sea",
  "into": null,
  "through": [
   "R3",
   "R6",
   "R4"
  ],
  "lakes": [],
  "length_km": 560
 },
 {
  "id": "V3",
  "name": "Merrow",
  "source": "R5",
  "mouth_region": "R1",
  "ends": "inland",
  "into": null,
  "through": [
   "R5",
   "R1"
  ],
  "lakes": [],
  "length_km": 342
 }
]

roads, decoded:

[
 {
  "id": "D1",
  "name": "King's Road",
  "kind": "road",
  "connects": [
   "P1",
   "P5",
   "P10"
  ],
  "through": [
   "R1",
   "R5",
   "R6"
  ],
  "crosses": [
   "V1"
  ],
  "length_km": 756
 },
 {
  "id": "D2",
  "name": "Southway",
  "kind": "trade",
  "connects": [
   "P7",
   "P10"
  ],
  "through": [
   "R6",
   "R7"
  ],
  "crosses": [],
  "length_km": 472
 }
]

checks, decoded:

{
 "findings": [
  {
   "key": "river_mouth:V3",
   "sev": "warn",
   "msg": "The Merrow ends inland in Haldmoor. Run it on to the coast, into a lake, or onto another river."
  },
  {
   "key": "river_uphill:V3:R5>R1",
   "sev": "warn",
   "msg": "The Merrow runs uphill from Merrow Fen (marsh) into Haldmoor (hills). Rivers fall from higher ground; reverse it or retag the terrain."
  }
 ],
 "disputes": [],
 "totals": {
  "regions": 8,
  "lakes": 0,
  "coastal": 7,
  "land_km2": 1447400,
  "places": 11,
  "rivers": 3,
  "roads": 2,
  "disputes": 0,
  "river_km": 1814,
  "road_km": 1228
 }
}

previous, decoded (the first of its 4 entries; the others have the same shape):

[
 {
  "region": "R3",
  "epithet": "the timber march of the cold north",
  "text": "Ostval is subarctic forest, and its Ostmen live in steep-roofed lodges of spruce, counting their years by hard winters and their debts in cords of wood. Rye and grain will not ripen under the Ostval pines, so families fell masts, render pitch and trap marten and fox for furs through the long dark. The Tess rises in Ostval, and each thaw the lumber guilds float rafts of logs down it to the plains of Tessaly Coast. Ostgate, the only port of Ostval, ships pitch and furs along the coast and takes in the salt and grain the forest cannot make. No road joins Ostval to the Kael Basin, so Ostmen pay whatever the Kaelmoot grain factors ask, and every lean spring they curse the moot by name. Ostval sent axemen to both sides in the Sundering War, and old families still hang the stump of a felled pine over the door of a house that chose wrong. Ostmen leave the first log of every raft standing upright on the Tess bank as a gift to the river, which they call the Grey Mother.",
  "trade": [
   {
    "with": "R4",
    "exports": [
     "timber",
     "pitch"
    ],
    "imports": [
     "salt",
     "cloth"
    ],
    "route": "V2"
   },
   {
    "with": "R6",
    "exports": [
     "furs",
     "charcoal"
    ],
    "imports": [
     "grain",
     "horses"
    ],
    "route": "border"
   }
  ],
  "tension": {
   "with": "R6",
   "over": "Kaelmoot sets the grain price Ostval must pay, and the forest has nothing the moot cannot buy elsewhere."
  },
  "landmark": {
   "place": "P3",
   "note": "Ostgate is Ostval's only harbour and the one door out of the forest that the grain factors do not hold."
  }
 }
]

changes, decoded:

{
 "R3": [
  "area 163800 -> 210200 km2 (+28%)"
 ],
 "R4": [
  "area 170800 -> 94000 km2 (-45%)"
 ],
 "R6": [
  "area 262600 -> 306600 km2 (+17%)",
  "new river V2"
 ],
 "R7": [
  "new place P11"
 ]
}

Reply (trimmed to 1 of 4 entries, 1 of 4 updated; the real reply has every item and the page shows it whole):

{
 "task": "lore",
 "title": "The Vellmark Reach",
 "headline": "Ostval, Tessaly Coast, Kael Basin and Duskmere Downs all ring the grain moot at Kaelmoot, and each one's fortune depends on how cheaply it can reach the Kael Basin's harvest.",
 "entries": [
  {
   "region": "R3",
   "epithet": "the timber march of the cold north",
   "sentences": [
    {
     "topic": "people",
     "text": "Ostval is subarctic forest, now the broadest woodland in the Reach, and its Ostmen live in steep-roofed lodges of spruce, counting their debts in cords of wood.",
     "refs": [
      "R3"
     ],
     "claims": [
      {
       "type": "terrain",
       "region": "R3",
       "value": "forest"
      },
      {
       "type": "climate",
       "region": "R3",
       "value": "subarctic"
      }
     ]
    },
    {
     "topic": "livelihood",
     "text": "Rye and grain will not ripen under the Ostval pines, so families fell masts, render pitch and trap marten and fox for furs through the long dark.",
     "refs": [
      "R3"
     ],
     "claims": []
    },
    {
     "topic": "trade",
     "text": "The Tess rises in Ostval and now runs through the Kael Basin before Tessaly Coast, so each thaw the lumber guilds float log rafts past the grain country to Tessmouth.",
     "refs": [
      "V2",
      "R3",
      "R6",
      "R4",
      "P4"
     ],
     "claims": [
      {
       "type": "river",
       "river": "V2",
       "region": "R3"
      },
      {
       "type": "river",
       "river": "V2",
       "region": "R6"
      },
      {
       "type": "river",
       "river": "V2",
       "region": "R4"
      }
     ]
    },
    {
     "topic": "landmark",
     "text": "Ostgate, the only port of Ostval, ships pitch and furs along the coast and takes in the salt and grain the forest cannot make.",
     "refs": [
      "P3",
      "R3"
     ],
     "claims": [
      {
       "type": "place",
       "place": "P3",
       "region": "R3"
      },
      {
       "type": "coastal",
       "region": "R3",
       "value": true
      }
     ]
    },
    {
     "topic": "tension",
     "text": "Ostval now shares a long border with the Kael Basin, and the moot taxes every raft that drifts down the Tess through its fields, which the Ostmen call theft of their own river.",
     "refs": [
      "R3",
      "R6",
      "V2"
     ],
     "claims": [
      {
       "type": "adjacent",
       "a": "R3",
       "b": "R6"
      },
      {
       "type": "river",
       "river": "V2",
       "region": "R6"
      }
     ]
    },
    {
     "topic": "history",
     "text": "Ostval sent axemen to both sides in the Sundering War, and old families still hang the stump of a felled pine over the door of a house that chose wrong.",
     "refs": [
      "R3"
     ],
     "claims": []
    },
    {
     "topic": "belief",
     "text": "Ostmen leave the first log of every raft standing upright on the Tess bank as a gift to the river, which they call the Grey Mother.",
     "refs": [
      "R3",
      "V2"
     ],
     "claims": [
      {
       "type": "river",
       "river": "V2",
       "region": "R3"
      }
     ]
    }
   ],
   "trade": [
    {
     "with": "R4",
     "exports": [
      "timber",
      "pitch"
     ],
     "imports": [
      "salt",
      "cloth"
     ],
     "route": "V2"
    },
    {
     "with": "R6",
     "exports": [
      "furs",
      "charcoal",
      "timber"
     ],
     "imports": [
      "grain",
      "horses"
     ],
     "route": "V2"
    }
   ],
   "tension": {
    "with": "R6",
    "over": "The Tess now carries Ostval timber through the Kael Basin, and Kaelmoot tolls the rafts as well as setting the price of Ostval bread."
   },
   "landmark": {
    "place": "P3",
    "note": "Ostgate is Ostval's only harbour and the one door out of the forest that the grain factors do not hold."
   }
  }
 ],
 "updated": [
  {
   "region": "R3",
   "change": "Ostval grew by 28% and now shares a 519 km border with the Kael Basin, which the Tess now crosses.",
   "kept": "The Ostmen, their spruce lodges, timber and pitch, Ostgate as landmark, the Grey Mother rite and the Sundering War history."
  }
 ],
 "notes": [
  "The map still flags the Merrow (ends inland in Haldmoor, runs uphill from Merrow Fen); no entry leans on it."
 ]
}

The page checks every claim; the API does not

Over the API you get the reply as JSON and nothing is checked for you. The page reconciles it against the gazetteer before showing it: every region, place, river and road id must exist; every adjacent, coastal, climate, terrain, river, place, road and lake claim is compared with the computed facts; a trade route of "border" must join neighbours and "sea" two coastal regions; a tension must be with a neighbour and a landmark must stand in its region. Trade proposals (add_road, add_sea_lane, add_port, add_dispute) are replayed on a copy of the world, and the map checks re-run: a proposal is ok, breaks (it adds a new map finding) or invalid.

A script can do the same with the world JSON exported from the page (Export → JSON: {"app":"realm-atlas","version":1,"world":{...},"lore":{...},"trade":...,"gazetteer":{...}}) and the page's own plain scripts, loaded in node after global.window = {}: geom.js, atlas.js and lore.js.

const fs = require("fs");
global.window = {};
for (const f of ["geom.js", "atlas.js", "lore.js"]) require("./" + f);
const { Atlas, Lore } = window;
const c = Atlas.compute(Atlas.normalize(JSON.parse(fs.readFileSync("my-world.json", "utf8"))).world);
const input = Atlas.buildInput(c, { task: "lore", focus: ["R3", "R6"] }).input;   // this exact run body
// ... run it, then:
const res = Lore.normalize(Lore.parseResult(replyText), "lore");
const report = Lore.reconcile(res, c, { focus: ["R3", "R6"] });   // flags per entry, sentence, trade line; per proposal for trade

Step 1 — a tiny client helper

One function that sends JSON and unwraps the envelope. Paste your personal token from /tokens.html where it says paste-your-token-here.

BASE="https://api.skillsafe.ai/v1/app-api"
TOKEN="paste-your-token-here"   # from https://realm-atlas.skillsafe.ai/tokens.html
api() { # api METHOD PATH [JSON-BODY]
  curl -s -X "$1" "$BASE$2" -H "Content-Type: application/json" \
    ${TOKEN:+-H "Authorization: Bearer $TOKEN"} ${3:+--data "$3"}
}

Step 2 — a token

A guest token is enough for /me and /estimate. Lore and trade runs are metered, so a run needs a personal token: sign in on /tokens.html and copy it from there.

# a guest token (enough for /me and /estimate); a run needs your personal token from /tokens.html
TOKEN=$(curl -s -X POST "$BASE/guest" -H "Content-Type: application/json" -d '{"slug":"realm-atlas"}' | jq -r '.data.token')

Step 3 — GET /me

Who the token is (subject_type: user or guest) and the credit balance.

api GET /me | jq '.data | {subject_type, credits}' 

Step 4 — POST /estimate

Free. The body is the run input itself (not wrapped in {"input": ...}). hold_credits is a reservation, not the price; min_credits is what the balance must cover; warnings is where a wrapper shows up as unknown field 'input' — read it before you pay.

# input.json: the run input object - {"task": "lore", "title": "...", "seed": "...", "scale": "...", "regions": "[...]", "places": "[...]", "rivers": "[...]", "roads": "[...]", "checks": "{...}", "focus": "[...]"}
api POST /estimate "$(cat input.json)" | jq '.data | {hold_credits, min_credits, model, model_alias, markup_bps, warnings}' 

Step 5 — POST /run, then poll the job

Send an Idempotency-Key built from the lane and the input: a retry with the same key never bills twice. The page uses realm-atlas:<task>:<input-hash>:a<attempt> (its own short hash of the input; any stable hash works). The job's output.output is the reply text, charged_credits what the run cost, and truncated: true means the balance cut it short.

KEY="realm-atlas:lore:$(shasum -a 256 input.json | cut -c1-16):a1"
JOB=$(curl -s -X POST "$BASE/run" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $KEY" --data @input.json | jq -r '.data.job_id')
while :; do
  J=$(api GET "/jobs/$JOB"); S=$(echo "$J" | jq -r '.data.status')
  case "$S" in succeeded|failed|cancelled) break;; esac; sleep 2
done
echo "$J" | jq -r '.data.output.output' > reply.json   # one JSON object, the reply contract
echo "$J" | jq '.data | {charged_credits, truncated}' 

Step 6 — POST /run-stream

The same run as Server-Sent Events, with the same input.json. Browsers receive progress ticks rather than text deltas; the final done event carries the reply.

# Server-Sent Events: tick/delta events while the job runs, then a final event with the job
curl -N -s -X POST "$BASE/run-stream" -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: realm-atlas:lore:$(shasum -a 256 input.json | cut -c1-16):a1" --data @input.json