Authenticatie
Alle API requests (behalve /login) vereisen een site header plus één van deze twee vormen van authenticatie:
| Methode | Header | Bedoeld voor | Geldigheid |
|---|---|---|---|
| JWT token | Authorization: Bearer <token> | Gebruikers en de frontend (inloggen met e-mail en wachtwoord) | 1 uur |
| API key | X-API-Key: <key> | Systeemkoppelingen (webshop, ERP, scripts) | Tot de key wordt uitgeschakeld of verwijderd |
Voor koppelingen met andere systemen is een API key de aanbevolen keuze: er hoeft niet elk uur opnieuw ingelogd te worden, er is geen MFA-stap, en de rechten zijn per key te beperken.
Als beide headers worden meegestuurd, wordt alleen de Authorization header gebruikt.
Login
Verkrijg een JWT token door in te loggen met e-mail en wachtwoord.
POST /login
Request
{
"email": "user@example.com",
"password": "your-password"
}
Response 200 OK
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"userId": "1",
"expiresIn": 3600,
"admin": false,
"site": "SITE01",
"name": "Jan Janssen",
"email": "user@example.com",
"client": null,
"fulfillment": false
}
| Veld | Type | Beschrijving |
|---|---|---|
token | string | JWT token, geldig voor 1 uur |
userId | string | Gebruikers-ID |
expiresIn | number | Geldigheid in seconden (3600 = 1 uur) |
admin | boolean | Of de gebruiker een admin is |
site | string | Site ID |
name | string | Volledige naam van de gebruiker |
email | string | E-mailadres |
client | string|null | Client ID (alleen bij fulfillment) |
fulfillment | boolean | Of de site een fulfillment site is |
Response 401 Unauthorized
{
"message": "User/password not correct"
}
MFA
Als MFA voor de site aan staat (setting mfa_mode: optional of required), geeft POST /login nog geen token maar een tijdelijk mfaToken (5 minuten geldig):
{
"mfaRequired": true,
"mfaToken": "eyJhbGciOiJIUzI1NiIs..."
}
Wissel dat in voor een JWT token met de code uit de authenticator-app:
POST /login/verify-mfa
{
"mfaToken": "eyJhbGciOiJIUzI1NiIs...",
"code": "123456"
}
De response is dan gelijk aan die van een gewone login. Krijg je mfaSetupRequired: true terug, dan moet de gebruiker MFA eerst instellen via de frontend.
In de authenticator-app staat het account als NextWMS met het e-mailadres van de gebruiker. Op de testomgeving heet het NextWMS dev, zodat een account op beide omgevingen niet als twee gelijke regels in de app staat. Dat geldt voor MFA die vanaf 15 oktober 2026 is ingesteld; een eerder ingestelde koppeling houdt de oude naam tot MFA opnieuw wordt ingesteld.
MFA opnieuw instellen
Een gebruiker met MFA kan het zelf opnieuw instellen in het profiel, bijvoorbeeld voor een nieuwe telefoon. Dat gaat in twee stappen, met een volledige login (Authorization: Bearer <token>):
POST /mfa/renew
{ "code": "123456" }
code is de huidige code uit de authenticator-app, of een backupcode. De response bevat een nieuwe QR-code (qrCode), het geheim om handmatig in te voeren (secret) en een renewToken, tien minuten geldig. De huidige koppeling blijft werken tot stap 2.
POST /mfa/renew/confirm
{ "renewToken": "eyJhbGciOiJIUzI1NiIs...", "code": "654321" }
code komt uit de nieuwe koppeling. Daarna werkt alleen de nieuwe koppeling, en de response bevat acht nieuwe backupcodes (backupCodes); de oude vervallen.
Een foute code of een verlopen renewToken geeft 400, niet 401: de gebruiker blijft ingelogd en kan het opnieuw proberen.
- Telefoon kwijt, wel backupcodes: log in met een backupcode in plaats van de code uit de app, en stel MFA daarna opnieuw in met een volgende backupcode. Elke backupcode werkt één keer.
- Telefoon kwijt en geen backupcodes: een beheerder met het recht op gebruikers zet MFA terug in het gebruikersscherm; bij de volgende login stelt de gebruiker MFA opnieuw in.
Token verificatie
Controleer of een bestaand token nog geldig is.
GET /verifytoken
Headers
Authorization: Bearer <token>
Response 200 OK
{
"message": "Token is valid"
}
Response 401 Unauthorized
{
"message": "Authorization token not valid"
}
Gebruik van het token
Voeg bij elke API request de volgende headers toe:
Authorization: Bearer <token>
site: <site-id>
Let op
Het token verloopt na 1 uur. Na verlopen moet opnieuw ingelogd worden via POST /login.
API keys
Een API key is een vaste sleutel voor één site, met per onderdeel ingestelde rechten. Hij verloopt niet.
Key aanmaken
API keys worden beheerd door een ingelogde gebruiker (JWT), via het API keys-scherm in de frontend of via de API. Een API key kan zelf geen keys aanmaken, wijzigen of opvragen.
POST /api-keys
Authorization: Bearer <token>
site: <site-id>
{
"name": "Webshop koppeling",
"permissions": [
{ "resource": "orders", "method": "GET" },
{ "resource": "orders", "method": "POST" },
{ "resource": "skus", "method": "GET" }
]
}
Optioneel: client_id (om de key aan een fulfillment-client te koppelen) en enabled (standaard true).
Een key met client_id werkt namens die opdrachtgever: een order via POST /orders en een preadvies via POST /preadvices krijgen altijd die opdrachtgever, GET /inventory/available toont alleen diens artikelen, en de endpoints onder /orders en /preadvices tonen en wijzigen alleen diens orders en preadviezen (een order of preadvies van een andere opdrachtgever geeft 404). Wie met zo'n key een andere opdrachtgever meestuurt, krijgt 403. Andere endpoints filteren nog niet op de opdrachtgever.
Response 201 Created
{
"id": 7,
"api_key": "nk_live_3f9a1c2b...",
"key_prefix": "nk_live_3f9a1c2b",
"name": "Webshop koppeling",
"message": "Bewaar deze API key veilig. Na het sluiten van dit scherm is de key niet meer op te vragen."
}
Let op
De volledige key (api_key) wordt alleen in deze response getoond. Er wordt alleen een hash opgeslagen; kwijt is kwijt, maak dan een nieuwe key aan.
Key gebruiken
X-API-Key: nk_live_3f9a1c2b...
site: <site-id>
curl https://api.nextwms.net/orders \
-H "X-API-Key: nk_live_3f9a1c2b..." \
-H "site: SITE01"
Een API key is geen gebruiker. Wat via een key wordt aangemaakt of geboekt, heeft daarom geen gebruiker: created_by bij orders en preadviezen en user_name bij voorraadmutaties zijn leeg.
Rechten
Elke request wordt gecontroleerd op onderdeel + HTTP-methode. Zonder bijbehorend recht volgt 403. De beschikbare onderdelen en methodes staan in GET /api-keys/resources:
Onderdeel (resource) | Endpoints | Methodes |
|---|---|---|
skus | /skus, /skugroups, /sync/products | GET, PUT, POST, DELETE |
locations | /locations, /putaway-algorithms, /allocation-algorithms, /pickmethods | GET, POST, PUT, DELETE |
preadvices | /preadvices | GET, POST, PUT, DELETE |
orders | /orders | GET, POST, PUT, DELETE |
inventory | /inventory | GET, POST |
receipt | /receipt | POST |
waves | /waves, /wave-definitions | GET, POST, DELETE |
tasks | /tasks, /shipdock | GET, POST |
customers | /customers, /clients | GET, POST, PUT, DELETE |
carriers | /carriers | GET, POST, PUT, DELETE |
suppliers | /suppliers | GET, POST, PUT, DELETE |
shipping | /shipping | GET, POST |
webhooks | /webhooks | GET, POST, PUT, DELETE |
labels | /labels | GET, POST, PUT, DELETE |
dashboard | /dashboard, /settings, /logs | GET |
Endpoints die niet in deze tabel staan (zoals /api-keys zelf) zijn niet bereikbaar met een API key.
Rechten van een bestaande key wijzigen: PUT /api-keys/:id/permissions met { "permissions": [...] } (vervangt de volledige lijst). Een key uitschakelen: PUT /api-keys/:id met "enabled": false. Verwijderen: DELETE /api-keys/:id.
Foutmeldingen
| Status | Message | Oorzaak |
|---|---|---|
401 | Invalid API key | Key onbekend voor deze site, of uitgeschakeld |
401 | No Authorization token or API key | Geen Authorization en geen X-API-Key header |
403 | API key does not have POST permission on 'orders' | Recht ontbreekt voor dit onderdeel/deze methode |
403 | API key access is not available for this endpoint | Endpoint is niet beschikbaar voor API keys |
403 | API keys cannot manage API keys | Key probeert /api-keys te gebruiken |
Rechten van gebruikers
Een ingelogde gebruiker (JWT) heeft de menurechten van zijn gebruikersgroep. De frontend toont daarmee alleen de schermen waar de gebruiker bij mag; de API controleert dezelfde rechten per endpoint. API keys vallen hier buiten: die hebben hun eigen rechten, zie hierboven.
Instelling rights_enforcement
De controle staat per site aan of uit, met een rij in settings:
| Waarde | Gedrag |
|---|---|
off | Geen controle. Dit geldt ook als de instelling ontbreekt. |
log | Niets wordt geweigerd. Wat geweigerd zou zijn, komt in het log (GET /logs) als regel die begint met Rechten:. |
enforce | Een request zonder het juiste recht krijgt 403. De weigering komt ook in het log. |
INSERT INTO settings (site_id, setting, value) VALUES ('<site-id>', 'rights_enforcement', 'log');
Een onbekende waarde werkt als off en geeft een waarschuwing in de serverlog. Een wijziging van de instelling of van iemands rechten werkt binnen 30 seconden door.
Aanzetten bij een bestaande site: begin met log, laat de gebruikers een aantal dagen normaal werken en bekijk de regels Rechten: in het log. Elke regel is een actie die bij enforce geweigerd wordt: de gebruiker mist een recht, of de regel voor die route klopt niet. Zet de instelling pas op enforce als er geen onverwachte regels meer bijkomen.
Wat wordt gecontroleerd
- Alle schrijvende endpoints (
POST,PUT,DELETE) vragen het recht van het scherm waar de actie bij hoort. Wordt een endpoint door meerdere schermen gebruikt, dan volstaat één van die rechten. - Gevoelige leesendpoints: gebruikers (
/mfa/users), gebruikersgroepen en rechten (/user-groups), API keys, webhooks en logs. - Overige leesendpoints staan open voor elke ingelogde gebruiker. Stamgegevens zoals SKU's en locaties worden door vrijwel elk scherm gelezen.
- Eigen account: wachtwoord wijzigen, MFA instellen en labels afdrukken mag elke ingelogde gebruiker.
POST /wrapper: alleen de actiestask/picktask,task/shipClaimsetenwave/reset, elk met het recht van het bijbehorende scherm. Elke andere actie wordt geweigerd.
Response 403 Forbidden
{
"message": "Onvoldoende rechten voor deze actie"
}
Site header
De site header is verplicht bij elke request. Dit identificeert de warehouse site waarvoor de data wordt opgevraagd. De waarde kan een site_id of een client_id zijn.
Als de site header ontbreekt of ongeldig is, wordt een 403-fout geretourneerd:
{
"message": "Missing header site"
}
{
"message": "Invalid site"
}