Repository navigation
EN First API
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.
forge new WikiDemo --slug wiki-demo --preset postgres-api
forge init
forge validate
forge doctor
forge routes
forge devWikiDemo 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.
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.
| 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 |
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.
This page describes the behavior at the pinned commits below. Recheck these contracts when upgrading.
main/framework/cli.pymain/framework/api_models.pymain/framework/routers/project.pymain/framework/security.pymain/framework/operations.py