mapis mock APIs, over REST
Define a fake backend with a few HTTP calls and point Studio Chat's api-tools at it. No repo, no deploy: an endpoint saved through the management API answers the next request.
- Endpoints — a method + path with one or more response rules (status, headers, templated body, optional
whencondition, delay).GET /clientes/:dni,POST /transferencias. - Resources — a name + a record schema, and you get a full CRUD REST resource with pagination, filters, search and sort, persisted until you reset.
/reclamos,/reclamos/:id.
https://mapis.stdcht.io/<project>/…. The management API lives at https://mapis.stdcht.io/api/… and takes X-Admin-Key.
For agents: https://mapis.stdcht.io/llms.txt is the complete operating guide as text (mental model, API reference, templates, demo recipes, a full example) — the raw material for a skill.
Quickstart
A="X-Admin-Key: $ADMIN_KEY"; M=https://mapis.stdcht.io/api
# 1. A project (it gets its own API key)
curl -s -X POST $M/projects -H "$A" -H 'content-type: application/json' \
-d '{"name": "Demo Banco", "slug": "banco"}'
# → { "base_url": "https://mapis.stdcht.io/banco", "auth": { "header": "X-API-Key", "key": "mk_…" }, … }
# 2. An endpoint with a curated deck: two DNIs answer differently, the rest derive from the DNI
curl -s -X POST $M/projects/banco/endpoints -H "$A" -H 'content-type: application/json' -d '{
"method": "GET", "path": "/clientes/:dni", "name": "consultar_cliente",
"responses": [
{ "when": { "params.dni": "30123456" }, "body": { "dni": "30123456", "nombre": "Martina López", "saldo": 152300.5, "segmento": "premium" } },
{ "when": { "params.dni": { "matches": "^\\d{7,8}$" } }, "seed": "{{ params.dni }}",
"body": { "dni": "{{ params.dni }}", "nombre": "{{ name() }}", "saldo": "{{ int(1000, 90000) }}", "segmento": "{{ pick(\"clasico\", \"gold\") }}", "alta": "{{ date(\"-400d\", \"date\") }}" } },
{ "status": 404, "body": { "error": "cliente_no_encontrado" } }
]
}'
# 3. A CRUD resource with generated data (day offsets, so the deck never expires)
curl -s -X POST $M/projects/banco/resources -H "$A" -H 'content-type: application/json' -d '{
"name": "reclamos", "count": 8,
"schema": { "id": "RCL-{{ pad(seq(\"rcl\", 1001), 5) }}", "dni": "{{ dni() }}", "motivo": "{{ pick(\"cargo duplicado\", \"tarjeta no llegó\") }}",
"estado": "{{ pick(\"ABIERTO\", \"EN_ANALISIS\", \"RESUELTO\") }}", "creado": "{{ date(\"-\" + int(1, 30) + \"d\", \"date\") }}" }
}'
# 4. Call the mock like any API
K="X-API-Key: mk_…"
curl -s https://mapis.stdcht.io/banco/clientes/30123456 -H "$K"
curl -s "https://mapis.stdcht.io/banco/reclamos?estado=ABIERTO&limit=5" -H "$K"
curl -s -X POST https://mapis.stdcht.io/banco/reclamos -H "$K" -H 'content-type: application/json' -d '{"dni": "30123456", "motivo": "cargo duplicado"}'
# 5. Before each demo
curl -s -X POST $M/projects/banco/reset -H "$A"
Management API (https://mapis.stdcht.io/api, header X-Admin-Key)
| Call | What it does |
|---|---|
GET /projects · POST /projects | List / create. Body: { name, slug?, description?, auth?: {mode: "api_key"|"none", header?, key?}, timezone?, log_limit? } |
GET /projects/:slug | Summary + endpoints + resources (+ the project's key) |
PUT /projects/:slug | Declarative upsert of the whole definition (the shape export returns). Config-as-code. |
PATCH /projects/:slug · DELETE /projects/:slug · POST …/rotate-key | Change meta / auth, delete everything, mint a new key |
GET|POST|PUT /projects/:slug/endpoints | List / add one or an array / replace all |
GET|PUT|PATCH|DELETE /projects/:slug/endpoints/:id | One endpoint |
GET|POST /projects/:slug/resources · GET|PATCH|DELETE …/resources/:name | CRUD resources; PATCHing seed/schema/count re-seeds |
GET|PUT|DELETE …/resources/:name/records | Read, replace or wipe the data behind a resource |
POST /projects/:slug/reset | Back to the seed: records, counters, logs (?keep_logs=1). Also POST https://mapis.stdcht.io/<slug>/__reset with the project key. |
GET /projects/:slug/logs?limit=50 · DELETE …/logs | What the assistant actually sent: method, path, body, matched rule, status, response |
GET /projects/:slug/export · POST /projects/import?replace=1 | Whole definition out / in (?include_key=1 to include the key) |
GET /projects/:slug/openapi.json | OpenAPI 3 description of the mock |
GET /projects/:slug/studiochat-tools | Ready-made Studio Chat api-tool payloads (manage_api_tools), keyed with the project key |
GET /helpers · POST /render | Template helpers reference; dry-run a template: { template, ctx?, seed? } |
Endpoint definition
{
"method": "POST", "path": "/transferencias", "name": "transferir", "description": "…",
"public": false, // true = no key needed (public pages, webhooks)
"request": { "body": { "required": ["cbu", "monto"], "properties": { "monto": { "type": "number", "minimum": 1 } } } }, // 400 when it doesn't match
"responses": [ // first rule whose "when" holds wins; last one without "when" = default
{ "when": { "body.monto": { "gt": 500000 } }, "status": 422, "body": { "error": "supera_limite", "limite": 500000 } },
{ "when": { "body.cbu": { "starts_with": "999" } }, "status": 404, "body": { "error": "cbu_inexistente" } },
{ "status": 201, "delay_ms": 800,
"body": { "id": "TRF-{{ pad(seq(\"trf\", 900001), 6) }}", "estado": "PENDIENTE", "monto": "{{ num(body.monto) }}", "fecha": "{{ now() }}", "acreditacion": "{{ date(\"+1d\", \"dmy\") }}" } }
]
}
Conditions (when): { "path": value } is a loose equality (40000 == "40.000"); operators eq ne gt gte lt lte in nin exists empty contains starts_with ends_with matches type; combine with any, all, not or a bare array (OR). Paths: params.*, query.*, body.*, headers.*, method, path.
Templates ({{ … }}) work in bodies, headers, seeds and schemas. A string that is exactly one expression keeps the value's type ("{{ int(1, 9) }}" → a number). Helpers: ids (uuid seq id derive derive_pick), random (int float amount bool pick sample chance), fake es-AR data (name first_name last_name email phone dni cuit cbu alias bank company city province address word words sentence paragraph), dates (now today date format_date shift days_between with offsets like +2d -3h +1M and formats iso date datetime time dmy human unix weekday month), strings, numbers (num round money format_number percent), collections and logic (len sum pluck find where default if eq gt …). seed on a rule makes every random helper deterministic for that input — the same DNI always gets the same customer.
Resource definition
{
"name": "turnos", "id_field": "id", "wrap": false, "readonly": false, "public": false,
"seed": [ { "id": "T-1", "paciente": "Martina López", "fecha": "{{ date(\"+2d\", \"date\") }}", "estado": "CONFIRMADO" } ], // curated deck, templated at seed time
"count": 12, // plus generated records
"schema": { "id": "T-{{ seq(\"t\", 100) }}", "paciente": "{{ name() }}", "fecha": "{{ date(int(1, 20) + \"d\", \"date\") }}", "estado": "{{ pick(\"PENDIENTE\", \"CONFIRMADO\") }}" }
}
Generated routes: GET /turnos (?page&limit&sort&order&search&fields, plus campo=valor, campo_gte, campo_lte, campo_ne, campo_like; X-Total-Count header), GET /turnos/:id, POST /turnos (schema fills what the body leaves out), PUT|PATCH /turnos/:id, DELETE /turnos/:id. An explicit endpoint on the same path wins over the resource route, so one verb can be customised while the rest stays generic.
Auth
Each project has auth.mode: api_key (default: a generated mk_… key in X-API-Key or Authorization: Bearer; the header name is configurable) or none. Endpoints and resources can be public. The admin key opens every mock too, for curl tests. Keys are demo credentials: readable through the management API on purpose.
Studio Chat · studiochat-workers/mapis