Skip to content

EN First API

Cavanshirpro edited this page Sep 19, 2026 · 1 revision

Your first API: project, credentials, CRUD and RPC

English · Türkçe · Languages / Diller

Scope: JSON API Forge 0.5.1 · Source review: 17 September 2026 · Language: English

Use an empty deployment directory with the server package installed and forge available. Run this disposable tutorial separately from live application data.

1. Create the application

forge new WikiDemo --slug wiki-demo --preset postgres-api
forge init
forge validate
forge doctor
forge routes
forge dev

WikiDemo is the directory name; wiki-demo is the API slug. postgres-api creates an items resource and uses SQLite unless you configure another URL. Database files are stored under data/; the API prefix is /api/wiki-demo/v1.

forge init generates WIKI_DEMO_BOOTSTRAP_ADMIN_KEY in your private .env file. It stops if that file already exists. Using --force to add a project also rotates existing managed secrets; read the CLI guide.

2. Create a persistent administrator key

With the server running, execute the following Python in another terminal. httpx is a server dependency. getpass hides the typed bootstrap value. A raw API key is returned only in its creation response; this example keeps it in memory.

from getpass import getpass
import httpx

base = "http://127.0.0.1:8000/api/wiki-demo/v1"
bootstrap = getpass("WIKI_DEMO_BOOTSTRAP_ADMIN_KEY from .env: ")

with httpx.Client(timeout=10, trust_env=False) as http:
    response = http.post(
        f"{base}/admin/api-keys",
        headers={"X-API-Key": bootstrap},
        json={"name": "wiki-admin", "roles": ["admin"]},
    )
    response.raise_for_status()
    admin_key = response.json()["api_key"]

    # Bootstrap is consumed after the first successful key creation.
    response = http.post(
        f"{base}/admin/api-keys",
        headers={"X-API-Key": admin_key},
        json={
            "name": "wiki-demo-client",
            "permissions": [
                "wiki-demo.items.list", "wiki-demo.items.read",
                "wiki-demo.items.create", "wiki-demo.items.update",
                "wiki-demo.items.delete", "system.meta.read",
            ],
        },
    )
    response.raise_for_status()
    app_key = response.json()["api_key"]
    headers = {"X-API-Key": app_key}

    created = http.post(f"{base}/items", headers=headers, json={"name": "First record"})
    created.raise_for_status()
    print("Created record:", created.json())

    listed = http.get(f"{base}/items", headers=headers, params={"limit": 10, "sort": "id"})
    listed.raise_for_status()
    item_id = listed.json()["items"][0]["id"]

    updated = http.patch(f"{base}/items/{item_id}", headers=headers, json={"name": "Updated"})
    updated.raise_for_status()
    deleted = http.delete(f"{base}/items/{item_id}", headers=headers)
    deleted.raise_for_status()

For normal operation, store the administrator key securely and give ordinary clients narrowly scoped application keys. Running this script again with the same consumed bootstrap credential is expected to fail. Do not delete identity data merely to reopen bootstrap access.

3. Inspect the generated project

File Contents
app/WikiDemo/app.json Name, slug, API prefix and application version
config/10-databases.json primary connection and environment reference
config/20-security.json Bootstrap and the admin role
config/30-performance.json Memory cache and rate limits
config/40-resources.json items table, fields and permissions

4. Add an RPC operation

Create app/WikiDemo/config/70-operations.json:

{
  "operations": [
    {
      "name": "items.rename",
      "permission": "wiki-demo.items.update",
      "transaction": true,
      "idempotency": true,
      "input_schema": {
        "type": "object",
        "required": ["id", "name"],
        "additionalProperties": false,
        "properties": {
          "id": {"type": "integer", "minimum": 1},
          "name": {"type": "string", "minLength": 1, "maxLength": 120}
        }
      },
      "statements": [
        {
          "sql": "UPDATE items SET name = :name WHERE id = :id",
          "params": {"id": "$body.id", "name": "$body.name"},
          "mode": "execute",
          "require_rowcount_min": 1,
          "require_rowcount_max": 1,
          "result_name": "updated"
        }
      ],
      "invalidate_resources": ["items"]
    }
  ]
}

Run forge validate. Create a new item, then send {"id":1,"name":"Via RPC"} to POST /api/wiki-demo/v1/rpc/items.rename with Idempotency-Key: rename-001. Replace 1 with the actual ID and send your authorized application key. Repeating the same key and body replays the first result. Reusing the key with a different body produces 409. Restart the server in a controlled manner if JSON reload is disabled.

The operation never accepts client-supplied SQL text. In a multi-tenant application, you must also add server-derived tenant/ownership constraints to the SQL WHERE clause.

Sources and verification

This page describes the behavior at the pinned commits below. Recheck these contracts when upgrading.


English home · All English pages · Version maintenance

Clone this wiki locally