Naslag
REST API
Eén API voor elke resource: sessie-, token- en publieke toegang, lezen, schrijven, export, import en bestanden.
Elke resource heeft dezelfde REST API. Definieer een resource en hij antwoordt op /api/v1/<resource>: tonen, lezen, aanmaken, wijzigen, verwijderen, exporteren en importeren, met de rechten van wie de aanroep doet. Leer het voor één resource en je kent het voor allemaal. De voorbeelden op deze pagina gebruiken projects.
| Request | Doet | Recht |
|---|---|---|
GET /api/v1/projects |
Records tonen, met filteren, sorteren, zoeken en pagineren | view-projects |
GET /api/v1/projects/<uuid> |
Eén record lezen | view-projects |
POST /api/v1/projects |
Een record aanmaken | create-projects |
POST /api/v1/projects/<uuid> |
Een record aanmaken met een id die je zelf kiest | create-projects |
PUT of PATCH /api/v1/projects/<uuid> |
De velden wijzigen die je stuurt | edit-projects |
PUT of PATCH /api/v1/projects |
Meerdere records in één request wijzigen | edit-projects |
DELETE /api/v1/projects/<uuid> |
Eén record verwijderen | delete-projects |
DELETE /api/v1/projects |
Alle records verwijderen | api_delete_resource_projects |
GET /api/v1/projects/export |
Alle records downloaden als JSON, CSV of TSV | export-projects |
POST /api/v1/projects/import |
Records aanmaken en wijzigen vanuit een bestand | import-projects |
manage-projects omvat elke handeling behalve alle records in één keer verwijderen; dat staat alleen het eigen recht daarvoor toe.
Wie de aanroep doet
Er zijn drie manieren om binnen te komen, en de rechten zijn voor elk hetzelfde.
| Aanroeper | Logt in met | Gebruikelijk voor |
|---|---|---|
| Je eigen pagina's en scripts | De sessie van de ingelogde gebruiker | Formulieren, inline bewerken, een applicatie op een route |
| Een ander systeem | Een token | Een app, een server, een integratie |
| Een anonieme bezoeker | Een uitdrukkelijke toestemming per handeling | Een contact- of aanmeldformulier |
Vanaf je eigen pagina's: nb.api
Code op de site zelf heeft geen token nodig. De browser stuurt de sessie van de ingelogde gebruiker mee, en de API past de rechten van die gebruiker toe, precies zoals in de admin. De pagina-shell levert een kleine client, nb.api, en het basisadres van de site in nb.base_url:
const list = await nb.api.get(`${nb.base_url}/api/v1/projects?filter=published:1&limit=12`);
const created = await nb.api.post(`${nb.base_url}/api/v1/projects`, {
title: 'Harbour Atlas',
sector: 'public',
});
await nb.api.put(`${nb.base_url}/api/v1/projects/${uuid}`, { published: true });
await nb.api.delete(`${nb.base_url}/api/v1/projects/${uuid}`);
Elke aanroep geeft het ingelezen JSON-antwoord terug, ook bij een fout, dus controleer success:
const answer = await nb.api.post(`${nb.base_url}/api/v1/leads`, form);
if (!answer.success) {
show_error(answer.message);
}
Elke andere client werkt op dezelfde manier, zolang hij de sessiecookie meestuurt: fetch(url, { credentials: 'same-origin' }) vanuit React, Vue of gewoon JavaScript. Een bezoeker die niet is ingelogd, heeft de rechten van een anonieme bezoeker, en dat zijn er standaard geen.
Vanuit een ander systeem: tokens
Een externe aanroeper logt in als Nimbly-gebruiker en krijgt een token.
1. Geef de aanroeper een eigen gebruiker. Maak een rol met alleen de rechten die nodig zijn, bijvoorbeeld view-projects voor een feed, en een gebruiker met die rol. Zet API access aan in het actiepaneel van de gebruiker in de admin, naast het bewerkformulier op /nb-admin/users/<uuid>; daarvoor is een rol nodig die gebruikers mag bewerken. Zet je het uit, dan werkt het token van die gebruiker meteen niet meer.
2. Vraag een token aan:
curl -X POST https://example.com/api/v1/auth/token \
-H "Content-Type: application/json" \
-d '{"email": "feed@example.com", "password": "…"}'
{
"token": "anSojGGKOveDuIypVGbx…",
"token_created": 1791940697,
"token_expires": 1792075420,
"code": 200,
"success": true,
"status": "ok"
}
3. Stuur het mee met elk request:
curl https://example.com/api/v1/projects \
-H "Authorization: Bearer $TOKEN"
Een token is 10 minuten geldig na de laatste vernieuwing. Vernieuw het voor die tijd met een GET op hetzelfde adres:
curl https://example.com/api/v1/auth/token -H "Authorization: Bearer $TOKEN"
Een fout wachtwoord geeft 401 INVALID_CREDENTIALS; een gebruiker zonder API-toegang geeft 403 ACCESS_DENIED. Bewaar de inloggegevens in de eigen geheimenkluis van de aanroeper, nooit in een repository.
Vanaf anonieme bezoekers: één handeling tegelijk
Een publiek formulier moet records kunnen aanmaken zonder dat iemand inlogt. Sta precies die handeling toe, en verder niets, met een route op ext/uri/api/v1/<resource>/index.tpl:
[#api-allow post leads#]
Elke andere handeling op de resource blijft dicht. Formulieren die met [#build-form#] zijn gemaakt, voegen een formuliersleutel en een honeypotveld toe, die de API controleert. Zie Formulieren.
Lezen
Een lijst
GET /api/v1/projects?filter=published:1&sort=year|numeric|desc&limit=12&offset=0
{
"projects": {
"4f1c9a2e7b": {"uuid": "4f1c9a2e7b", "title": "Harbour Atlas", "sector": "public", "year": 2026, "published": true, "_created": 1792074820, "_modified": 1792074830},
"9d03b6e1c4": {"uuid": "9d03b6e1c4", "title": "Northlight Library", "sector": "culture", "year": 2026, "published": true, "_created": 1792074821, "_modified": 1792074821}
},
"count": 2,
"total": 9,
"code": 200,
"success": true,
"status": "ok"
}
Records zijn een object met de UUID als sleutel. count is het aantal dat terugkomt; total, aanwezig als je filtert, sorteert of pagineert, is het aantal vóór het afknippen.
| Parameter | Voorbeeld | Doet |
|---|---|---|
filter |
status:open, status:!draft, status:new||todo, published:1,sector:public |
Houdt passende records over. ! keert om, || is of, een komma is en |
sort |
year|numeric|desc, title|text|asc, date|desc,title|asc |
Sorteert op één of meer velden |
limit, offset |
limit=20&offset=40 |
Eén pagina: de derde pagina van 20 |
search |
search=harbour |
Records waarvan de titel de tekst bevat; in een resource zonder titelveld, elk veld |
Een gefilterde, gesorteerde of gepagineerde lijst leest alleen de records op die pagina, ook bij een grote resource. Filteren of sorteren op een veld dat de API nooit teruggeeft, wordt geweigerd met 400.
Eén record
GET /api/v1/projects/4f1c9a2e7b
Het antwoord heeft dezelfde vorm als een lijst, met één record.
Ongewijzigde data
Leesacties antwoorden met een header Last-Modified. Stuur die terug als If-Modified-Since, dan antwoordt een ongewijzigd record of een ongewijzigde lijst met 304 Not Modified, zonder inhoud.
Schrijven
Stuur JSON met Content-Type: application/json. Waarden worden gevalideerd tegen de definitie van de resource, opgemaakte tekst wordt opgeschoond tot de toegestane markup, en elke wijziging komt in de geschiedenis van het record, met de gebruiker die hem deed.
Aanmaken
POST /api/v1/projects
{"title": "Harbour Atlas", "sector": "public", "year": 2026}
{
"projects": {"1ztuvo56o5vze6t0": {"uuid": "1ztuvo56o5vze6t0", "title": "Harbour Atlas", "sector": "public", "year": 2026}},
"count": 1,
"message": "RESOURCE_CREATED",
"code": 201,
"success": true,
"status": "ok"
}
Nimbly maakt de UUID. Wil je een id uit een ander systeem gebruiken, zet die dan in het adres, POST /api/v1/projects/erp-1042, of stuur hem mee als uuid in de inhoud. Een id die al bestaat, geeft 409 RESOURCE_EXISTS; een ontbrekend verplicht veld of een ongeldige waarde geeft 422 INVALID_DATA.
Wijzigen
PUT /api/v1/projects/1ztuvo56o5vze6t0
{"published": true}
Alleen de velden die je stuurt, veranderen. PATCH doet precies hetzelfde; gebruik wat je client verwacht. Het antwoord bevat het hele record zoals het nu is, met RESOURCE_UPDATED. Een resource met "upsert": true in de .meta maakt het record aan als het nog niet bestaat.
Meerdere records in één request, met de UUID als sleutel:
PUT /api/v1/projects
{
"1ztuvo56o5vze6t0": {"published": true},
"9d03b6e1c4": {"published": false}
}
Verwijderen
DELETE /api/v1/projects/1ztuvo56o5vze6t0
geeft RESOURCE_DELETED. Het record is nog 90 dagen terug te zetten vanuit de geschiedenis in de admin. DELETE /api/v1/projects verwijdert alle records en heeft een eigen recht nodig, api_delete_resource_projects, dat in geen enkel ander recht zit.
Exporteren en importeren
Exporteren
GET /api/v1/projects/export?format=csv
Downloadt alle records als een bestand met de naam van de resource en de datum. format is json (de standaard), csv of tsv. De JSON-export bevat de definitie van de resource als _meta naast de records, zodat je de resource ergens anders opnieuw kunt maken. Velden die de API nooit teruggeeft, blijven weg.
Importeren
curl -X POST https://example.com/api/v1/projects/import \
-H "Authorization: Bearer $TOKEN" \
-F "file=@projects.csv"
{"stats": {"imported": 2, "errors": 0, "created": 1, "updated": 1, "total": 2}, "code": 200, "success": true, "status": "ok"}
Het bestand is JSON (een export, een object met records, of een lijst), CSV of TSV met een kopregel met veldnamen. Een rij wijzigt een bestaand record als de uuid past, en anders de id, name of title; andere rijen maken nieuwe records. Een JSON-export die wordt geïmporteerd in een site zonder die resource, maakt de resource aan vanuit de _meta.
Een document importeren
POST /api/v1/projects/import-document met een .docx-bestand stelt veldwaarden voor een nieuw record voor, door een AI-model uit het document gelezen. Dit is wat de actie Import document in de admin gebruikt. Het heeft create-projects en een OpenAI-sleutel in .env nodig, en maakt zelf niets aan.
Bestanden
Uploads gaan via .files naar de mediabibliotheek:
curl -X POST https://example.com/api/v1/.files \
-H "Authorization: Bearer $TOKEN" \
-F "file=@cover.jpg"
{
"files": {"uuid": "415efabde63801837fd88c62078f5db0", "name": "cover.jpg", "type": "image/jpeg", "size": 248113, "width": 2400, "height": 1600, "orientation": "landscape"},
"message": "RESOURCE_CREATED",
"code": 201
}
De UUID is de checksum van het bestand, dus hetzelfde bestand twee keer uploaden geeft dezelfde UUID. Bewaar die in een veld image, gallery of file, net als elke andere waarde.
| Request | Doet |
|---|---|
GET /api/v1/.files |
Alle bestanden tonen met hun metadata |
GET /api/v1/.files/<uuid> |
De metadata van één bestand lezen |
PUT /api/v1/.files/<uuid> |
Metadata wijzigen, zoals title |
DELETE /api/v1/.files/<uuid> |
Het bestand, de metadata en de verkleinde kopieën verwijderen |
GET /img/<uuid>/<size> |
De afbeelding, verkleind: 800w, 400h, 400x300c (uitsnede), 400x300f (passend), als WebP |
GET /download/<uuid> |
Het bestand zoals geüpload, met naam en type |
Rechten voor bestanden stel je in op de rij Files van het rollenscherm. Een bestand dat groter is dan de uploadlimiet van de server, geeft 413 UPLOAD_TOO_LARGE.
Antwoorden en fouten
Elk antwoord is JSON met code, success en status naast de data:
{
"message": "ACCESS_DENIED",
"needs": "api_get_projects,api_(any)_projects,api_get_(any),api_(any),view-projects,manage-projects",
"code": 403,
"success": false,
"status": "error"
}
| Code | Melding | Betekent |
|---|---|---|
| 200 | RESOURCE_UPDATED, RESOURCE_DELETED |
Gedaan |
| 201 | RESOURCE_CREATED |
Aangemaakt |
| 304 | Niet gewijzigd sinds If-Modified-Since |
|
| 400 | INVALID_DATA |
Het request klopt niet, of filtert op een verborgen veld |
| 401 | INVALID_CREDENTIALS |
Fout e-mailadres of wachtwoord bij een tokenaanvraag |
| 403 | ACCESS_DENIED |
Niet toegestaan; needs noemt de rechten die het zouden toestaan |
| 404 | RESOURCE_NOT_FOUND |
Die resource of dat record bestaat niet |
| 405 | METHOD_NOT_ALLOWED |
De resource ondersteunt deze methode niet |
| 409 | RESOURCE_EXISTS |
Er bestaat al een record met deze id |
| 413 | UPLOAD_TOO_LARGE |
Het bestand is groter dan de uploadlimiet |
| 422 | INVALID_DATA |
Een waarde voldoet niet aan de validatie van de resource |
| 500 | RESOURCE_CREATE_FAILED, RESOURCE_UPDATE_FAILED |
De server kon niet schrijven |
Controleer success en code in plaats van één bepaalde melding.
Wat de API nooit teruggeeft
Velden in hidden in de .meta, en eenzijdig versleutelde velden zoals wachtwoorden, worden bewaard en gebruikt, maar verschijnen nooit in een lijst, een record, een antwoord op een schrijfactie of een export. Bij gebruikers zijn het wachtwoord, de salt, het API-token en de hersteltokens altijd verborgen.
Eigen endpoints
Heeft een integratie een antwoord nodig dat de resource-API niet geeft, schrijf dan een eigen route. api_access('<permission>') accepteert alle drie soorten aanroepers, dus één endpoint bedient je eigen pagina's en andere systemen. Zie Integraties en Wat een route teruggeeft.
OpenAPI
Elke Nimbly-site beschrijft zijn eigen API volgens de standaard OpenAPI 3, op /api/v1/openapi.json. Het document noemt de resources van de site met hun velden, typen, verplichte velden en keuzeopties, en de beschikbare handelingen. Het volgt de rechten van de aanroeper, precies zoals de API: een token of sessie ziet alleen de resources en handelingen die hij mag gebruiken, velden die de API nooit teruggeeft, blijven weg, en een anoniem request wordt geweigerd.
curl https://example.com/api/v1/openapi.json -H "Authorization: Bearer $TOKEN" > openapi.json
Laad het in Postman, Insomnia of Swagger UI, of maak er met een OpenAPI-generator een getypeerde client van. Geef een integratie een eigen gebruiker, dan laat het document precies zien wat die integratie kan.
Een algemene beschrijving van de handelingen die elke resource deelt, met export, import en bestanden, staat hier.
Interne endpoints
Een paar adressen onder /api/v1/ dienen de admin en de bewerktools: .resources, .pages, .files-usage, .files-unused, .system-messages, session, openai/complete, openai/translate, git-status, git-pull, empty-resource, agent-chat en agent-remote. Ze controleren rechten zoals elk ander endpoint, maar hun vorm kan meeveranderen met de admin; bouw liever op de resource-API.