# Context pack: Fouten en snelheidslimieten

Source: https://nordvec.com/nl/docs/guides/errors-and-rate-limits
Pack: https://nordvec.com/nl/docs/packs/errors-and-rate-limits

This pack bundles one Nordvec guide with the guides it builds on and the guides it links to, in reading order, so an assistant reading it meets no reference it cannot follow.

## Contents

1. [Fouten en snelheidslimieten](https://nordvec.com/nl/docs/guides/errors-and-rate-limits) (this guide)
2. [MCP](https://nordvec.com/nl/docs/guides/mcp) (linked from this guide)

---

# Fouten en snelheidslimieten
Source: https://nordvec.com/nl/docs/guides/errors-and-rate-limits

De foutenvelop die elke mislukte aanvraag retourneert, de snelheidslimiet-headers en hoe je een schrijfopdracht veilig opnieuw kunt proberen.



Elke endpoint faalt op dezelfde manier, dus een client handelt fouten, snelheidslimieten en pogingen opnieuw af door die code één keer te schrijven en overal te hergebruiken, ook via [MCP](/docs/guides/mcp).

## De foutenvelop [#de-foutenvelop]

Elke niet-2xx-respons is één JSON-object:

```json
{
  "defined": false,
  "code": "TOO_MANY_REQUESTS",
  "message": "Too many requests",
  "data": { "reason": "rate_limit.exceeded", "retryAfterMs": 12000 }
}
```

* `code` is de fout op HTTP-niveau, bijvoorbeeld `UNAUTHORIZED`, `FORBIDDEN`,
  `NOT_FOUND`, `BAD_REQUEST` of `TOO_MANY_REQUESTS`.
* `data.reason`, indien aanwezig, is een preciezere machineleesbare reden zoals
  `auth.key_not_found` of `rate_limit.exceeded`. Baseer je hierop in plaats van op
  `message`, dat voor mensen is en kan veranderen.
* `defined` is `true` wanneer de bewerking die fout vermeldt in de
  [API-referentie](/docs/api), en `false` voor fouten die elke aanvraag kan tegenkomen
  (authenticatie, snelheidslimieten, een onbekende route).
* Een validatiefout antwoordt met `BAD_REQUEST` en de problemen in
  `data.formErrors` en `data.fieldErrors`.

Elke respons bevat ook een `X-Request-ID`. Vermeld deze wanneer je contact opneemt met support, zodat we die exacte aanvraag kunnen terugvinden.

## Gangbare statussen [#gangbare-statussen]

| Status | Code                    | Wat te doen                                                                    |
| ------ | ----------------------- | ------------------------------------------------------------------------------ |
| `400`  | `BAD_REQUEST`           | Corrigeer de aanvraag; `data.fieldErrors` benoemt de velden                    |
| `401`  | `UNAUTHORIZED`          | Verstuur een geldige sleutel of sessie                                         |
| `403`  | `FORBIDDEN`             | De sleutel heeft niet de vereiste scope of rol voor de bewerking               |
| `404`  | `NOT_FOUND`             | De resource bestaat niet, of je hebt geen toestemming om deze te zien          |
| `409`  | `CONFLICT`              | Een dubbele schrijfbewerking is nog bezig; probeer het kort daarna opnieuw     |
| `413`  | `PAYLOAD_TOO_LARGE`     | De aanvraagbody is groter dan 1 MB; splits een bulkpush op in kleinere batches |
| `422`  | `UNPROCESSABLE_CONTENT` | De aanvraag is correct gevormd maar kan niet worden toegepast                  |
| `429`  | `TOO_MANY_REQUESTS`     | Wacht `Retry-After`, probeer het dan opnieuw                                   |

## Snelheidslimieten [#snelheidslimieten]

Elke respons geeft de limiet aan waartegen deze is geteld, in twee vormen:

* de `X-RateLimit-*` headers;
* de IETF gestructureerde velden `RateLimit` (live status: `r` is het aantal resterende aanvragen, `t` de seconden tot het venster reset) en `RateLimit-Policy` (de quota: `q` is de limiet, `w` het venster in seconden).

Een `429` bevat ook `Retry-After` in seconden en `data.retryAfterMs`. Wacht minstens zo lang voordat je de volgende aanvraag doet; eerder opnieuw proberen wordt geteld en opnieuw geweigerd.

## Schrijfbewerkingen veilig opnieuw proberen [#schrijfbewerkingen-veilig-opnieuw-proberen]

Een schrijfbewerking die een `Idempotency-Key` header vermeldt in de
[API-referentie](/docs/api), kan opnieuw worden geprobeerd zonder het werk dubbel uit te voeren. Verstuur één sleutel per logische schrijfbewerking en herhaal dezelfde sleutel bij elke poging opnieuw:

* dezelfde sleutel met dezelfde body binnen 24 uur speelt de opgeslagen respons opnieuw af;
* dezelfde sleutel met een andere body wordt geweigerd met `422`;
* een duplicaat dat aankomt terwijl de eerste nog loopt, krijgt `409`.

Een bewerking zonder de header is niet idempotent, dus probeer deze alleen opnieuw als je zeker weet dat de eerste poging niet is geland.


---

# MCP
Source: https://nordvec.com/nl/docs/guides/mcp

Koppel een AI-agent aan jouw werkruimte-kennisbank via het Model Context Protocol met een API-sleutel.



Nordvec ondersteunt het [Model Context Protocol](https://modelcontextprotocol.io) (MCP), dus een agent of assistent die MCP spreekt, kan de operaties van jouw werkruimte ontdekken als tools en deze native aanroepen, zonder dat er een OpenAPI-document nodig is om over na te denken.

Er zijn twee servers:

| Server       | URL                              | Auth        | Wat het blootstelt                                                                                                            |
| ------------ | -------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------- |
| Documentatie | `https://nordvec.com/api/mcp`    | geen        | Deze handleidingen en de connectorcatalogus, voor een agent die integreert met Nordvec                                        |
| Kennisbank   | `https://nordvec.com/api/v1/mcp` | API-sleutel | De documenten, zoek- en opnamefuncties van jouw werkruimte: dezelfde operaties als de [REST API](/docs/guides/authentication) |

Beide draaien in de EU, op dezelfde infrastructuur als de rest van de API.

## Een client verbinden [#een-client-verbinden]

Richt een MCP-client op de kennisbankserver met jouw API-sleutel als bearer-token. De meeste clients accepteren een configuratieblok zoals dit:

```json
{
  "mcpServers": {
    "nordvec": {
      "url": "https://nordvec.com/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer nv_eu_live_your_api_key"
      }
    }
  }
}
```

De server is stateless Streamable HTTP: elk bericht is één `POST` met één JSON-RPC-verzoek, en het antwoord komt terug in de response-body. Er zijn geen sessies om te onderhouden en geen servergeïnitieerde stream, dus een `GET` op de URL antwoordt met `405`, en een `POST` waarvan de `Content-Type` niet `application/json` is, antwoordt met `415` voordat de body wordt gelezen.

<Callout type="warn">
  Alleen een API-sleutel kan de kennisbankserver gebruiken. Een ingelogde browsersessie wordt geweigerd, en elke tool heeft de sleutelscope nodig die de bijbehorende REST-operatie vereist, dus een sleutel die voor één taak is aangemaakt, kan precies die taak ook via MCP uitvoeren.
</Callout>

De server authenticeert met een statische bearer-sleutel en biedt geen OAuth-discovery. Een client waarmee je request-headers kunt instellen (codeeragents, IDE-extensies, de MCP Inspector in header-modus) verbindt zoals hierboven getoond; een gehoste client die alleen de OAuth-authorization-flow ondersteunt, kan nog geen verbinding maken.

Een client die de `MCP-Protocol-Version`-header verstuurt, krijgt een antwoord onder die versie wanneer de server deze ondersteunt (`2025-06-18` en `2024-11-05`) en wordt geweigerd met `400` wanneer dat niet het geval is, zodat een versieconflict bij het eerste bericht wordt gemeld in plaats van later als een onjuist antwoord.

## Tools [#tools]

De tools zijn afgeleid van de REST API, één tool per operatie die een API-sleutel mag aanroepen. De naam van een tool is het contractpad van de operatie in snake case, waarbij een segment dat het voorgaande herhaalt, wordt weggelaten. De scopekolom is de API-sleutelscope die de tool nodig heeft:

| REST-bewerking                        | Contractpad                          | MCP-tool                           | Bereik         |
| ------------------------------------- | ------------------------------------ | ---------------------------------- | -------------- |
| `POST /documents/search`              | `documents.search`                   | `documents_search`                 | `search:read`  |
| `GET /documents/{id}`                 | `documents.get`                      | `documents_get`                    | `search:read`  |
| `POST /documents/batch`               | `documents.batchGet`                 | `documents_batch_get`              | `search:read`  |
| `GET /documents/list`                 | `documents.list`                     | `documents_list`                   | `search:read`  |
| `POST /documents/push`                | `documentPush.push`                  | `document_push`                    | `index:write`  |
| `POST /documents/push/bulk`           | `documentPush.pushBulk`              | `document_push_bulk`               | `index:write`  |
| `POST /documents/push/permissions`    | `documentPush.pushUpdatePermissions` | `document_push_update_permissions` | `index:write`  |
| `POST /documents/push/delete`         | `documentPush.pushDelete`            | `document_push_delete`             | `index:delete` |
| `GET /documents/push/status`          | `documentPush.pushStatus`            | `document_push_status`             | `index:status` |
| `GET /documents/push/upload`          | `documentPush.pushUploadStatus`      | `document_push_upload_status`      | `index:status` |
| `POST /documents/push/upload/restore` | `documentPush.pushUploadRestore`     | `document_push_upload_restore`     | `index:write`  |
| `GET /analytics/ingestion`            | `analytics.ingestion`                | `analytics_ingestion`              | `index:status` |
| `GET /quota/embedding`                | `quota.embedding`                    | `quota_embedding`                  | `quota:read`   |
| `GET /analytics/usage`                | `analytics.usage`                    | `analytics_usage`                  | `quota:read`   |
| `POST /tenant/audit-log/search`       | `auditLog.list`                      | `audit_log_list`                   | `audit:read`   |
| `GET /tenant/audit-log/catalog`       | `auditLog.catalog`                   | `audit_log_catalog`                | `audit:read`   |

`tools/list` is de gezaghebbende catalogus: deze toont alleen de tools die de scopes van een sleutel toestaan, en de beschrijving van elke tool vermeldt de scope die deze vereist. Het `inputSchema` van een tool is het request-schema van de operatie en, wanneer de operatie een object retourneert, is het `outputSchema` het response-schema en bevatten de resultaten `structuredContent` naast de JSON-tekst.

Het aanroepen van een tool die niet is toegestaan door de scopes van de sleutel, resulteert in een tool-fout die `FORBIDDEN` vermeldt, dezelfde weigering als de REST-route geeft. Zo leert een client met een gecachte lijst van een andere sleutel waarom de tool ontbreekt, in plaats van alleen dat deze ontbreekt.

## Een schrijfopdracht opnieuw proberen [#een-schrijfopdracht-opnieuw-proberen]

`document_push` en `document_push_bulk` accepteren een optioneel `idempotencyKey`
argument, zodat een agent of client die een push opnieuw probeert, de
documenten niet dubbel indexeert. Stuur één sleutel per logische schrijfopdracht,
zoals een UUID, en herhaal dezelfde sleutel met dezelfde argumenten bij elke
poging:

* dezelfde sleutel met dezelfde argumenten binnen 24 uur retourneert het resultaat van de eerste oproep zonder deze opnieuw uit te voeren;
* dezelfde sleutel met verschillende argumenten wordt geweigerd: het foutresultaat heeft `isError: true` en vermeldt `UNPROCESSABLE_CONTENT`;
* een herhalingspoging die arriveert terwijl de eerste oproep nog loopt, wordt op dezelfde manier geweigerd, waarbij `CONFLICT` wordt vermeld; probeer het na een korte vertraging opnieuw.

Een sleutel bestaat uit 1 tot 256 afdrukbare ASCII-tekens en behoort tot de API-sleutel die hem heeft verzonden: een andere API-sleutel die dezelfde waarde gebruikt, voert zijn eigen schrijfopdracht uit. Een sleutel die wordt gebruikt met de REST `Idempotency-Key` header, wordt niet gedeeld met de MCP-tool. Daarom wordt een toolaanroep die hem hergebruikt, geweigerd met `UNPROCESSABLE_CONTENT`. Een aanroep zonder het argument wordt elke keer opnieuw uitgevoerd, wat de reden is dat deze tools `idempotentHint` niet declareren. Het REST-gedrag wordt beschreven onder [veilig opnieuw schrijven](/docs/guides/errors-and-rate-limits).

## Limieten en fouten [#limieten-en-fouten]

Een tool-aanroep gebruikt dezelfde rate-limit-bucket als de bijbehorende REST-operatie, en elk ander bericht gebruikt de eigen bucket van het endpoint. De `X-RateLimit-*`-headers, het `429`-antwoord met zijn `Retry-After`, en de foutenvelop zijn hetzelfde als bij de [REST API](/docs/guides/errors-and-rate-limits), dus een client die deze al voor REST afhandelt, kan dit hier ook doen.

Een mislukte tool-aanroep retourneert een MCP-toolresultaat met `isError: true` waarvan de tekst het REST-foutlichaam is (`code`, `message`, `data`); validatiefouten hebben dezelfde `fieldErrors`-vorm als de REST API retourneert. Een JSON-RPC-fout is gereserveerd voor het protocol zelf: een onleesbare body, een onbekende methode of een serverfout.

Request-bodies zijn beperkt tot 1 MB, dezelfde limiet als de REST-routes.

## Een eerste uitwisseling [#een-eerste-uitwisseling]

```bash
# Discover the tools your key can call
curl https://nordvec.com/api/v1/mcp \
  -H "Authorization: Bearer $NORDVEC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

# Search the workspace
curl https://nordvec.com/api/v1/mcp \
  -H "Authorization: Bearer $NORDVEC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"documents_search","arguments":{"query":"data retention policy"}}}'
```

## De documentatieserver [#de-documentatieserver]

De documentatieserver op `/api/mcp` heeft geen sleutel nodig. Deze biedt `list_guides`, `get_guide`, `search_docs` en `list_connectors`, zodat een agent die een applicatie op de API bouwt, deze handleidingen direct kan lezen. Deze is rate-limited per IP, net als de andere publieke endpoints.
