Getting started

Get an access token with an API key and call the NOCCIE API

The API uses the OAuth 2.0 client_credentials grant. You exchange an API key for an access token, then send the token as a bearer token. There are no user logins or one-time codes involved: the key belongs to the tenant, not to a person.

Base URL

The API is served from https://api.noccie.app. The examples below use that address.

Get an API key

A tenant administrator with the permission to manage API keys creates the key in NOCCIE. See API-sleutels beheren.

Creating a key gives you two values:

ValueExampleNotes
Key id (client_id)tk_ followed by a long random stringNot secret. Identifies the key.
Key secret (client_secret)A long random stringSecret. Shown once, when the key is created. NOCCIE cannot show it again.

The key also has scopes (what it may do) and optionally an expiry date. A key is bound to one tenant and only works for that tenant.

Request an access token

Send a POST request to /oauth/token with grant_type=client_credentials and your credentials. You can send them in any of these three ways.

JSON body
curl -X POST https://api.noccie.app/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "tk_YOUR_KEY_ID",
    "client_secret": "YOUR_KEY_SECRET"
  }'
Form body
curl -X POST https://api.noccie.app/oauth/token \
  -d grant_type=client_credentials \
  -d client_id=tk_YOUR_KEY_ID \
  -d client_secret=YOUR_KEY_SECRET
HTTP Basic
curl -X POST https://api.noccie.app/oauth/token \
  -u tk_YOUR_KEY_ID:YOUR_KEY_SECRET \
  -d grant_type=client_credentials

grant_type is always required. If you use the Basic header, either leave the credentials out of the body or send exactly the same client_id and client_secret. Otherwise the request is rejected.

A successful response looks like this:

{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "view_product",
  "tenant_id": "00000000-0000-0000-0000-000000000000"
}

tenant_id is the tenant your key belongs to. You need it in the path of every call.

Call the API

Send the token in the Authorization header. Use the tenant_id from the token response in the path. A key can only reach its own tenant: any other tenant id is rejected.

curl "https://api.noccie.app/tenant/TENANT_ID/product?type=service,set" \
  -H "Authorization: Bearer ACCESS_TOKEN"

See List products for the parameters and the response.

OpenAPI specification

The full API is described in an OpenAPI specification: openapi.json. Use it to import all endpoints into a tool such as Postman or Insomnia, or to generate a client with a code generator. It is always in sync with this reference.

Use Postman

Postman has built-in support for this grant, so it can fetch and refresh the token for you.

To get all endpoints at once, import the OpenAPI specification first (Import → Link, or upload the downloaded file).

  1. Open your request or collection, go to the Authorization tab and choose OAuth 2.0.
  2. Under Configure New Token, fill in:
Postman fieldValue
Grant typeClient Credentials
Access Token URLhttps://api.noccie.app/oauth/token
Client IDYour key id (tk_...)
Client SecretYour key secret
ScopeLeave empty. The scopes belong to the key, not to the request.
Client AuthenticationSend as Basic Auth header or Send client credentials in body
  1. Click Get New Access Token, then Use Token. Postman sends it as Authorization: Bearer ....

The token response contains your tenant_id. Copy it from the token details in Postman and store it in a variable, for example {{tenantId}}, so you can call https://api.noccie.app/tenant/{{tenantId}}/product.

Keep the secret out of shared collections

Store the key id and secret as variables in a Postman environment (mark the secret as a secret type variable) and reference them as {{clientId}} and {{clientSecret}}. Do not save them in a collection you share.

Token lifetime and caching

  • A token is valid for one hour (expires_in is 3600 seconds).
  • Request a token once and reuse it for all calls in that hour. Do not request a new token for every API call: the token endpoint is rate limited.
  • Request a new token shortly before the old one expires, or when you get a 401 back.
  • Treat the token as an opaque string. Do not read scopes or other data from inside it.

Storing the secret

Keep the key secret in an environment variable or a secret store on your server. Never put it in source code, a repository, a browser app or a mobile app: anyone who can read it can use your key. The key belongs on a server.

Token handling pattern

  • Keep one token per process in memory and reuse it.
  • Refresh it a few minutes before expires_in runs out.
  • On a 401, fetch one new token and retry the request once. A second 401 is a real error: the key is revoked or expired, or the scope is missing.
  • On a 429, wait and retry with the same token. Do not request new tokens in a loop.
  • Never log the secret or the token.

Node.js example

This example needs Node.js 18 or later and no packages. Optionally set NOCCIE_API_HOST (the API host without https://, default api.noccie.app), and set NOCCIE_KEY_ID, NOCCIE_KEY_SECRET and NOCCIE_TENANT_ID in the environment, then run it with node example.js.

example.js
const { NOCCIE_KEY_ID, NOCCIE_KEY_SECRET, NOCCIE_TENANT_ID } = process.env;
const API_HOST = process.env.NOCCIE_API_HOST ?? 'api.noccie.app';
const base = `https://${API_HOST}`;

let cached = null; // { token, expiresAt }
const REFRESH_MARGIN_MS = 5 * 60 * 1000;

async function getToken(force = false) {
  if (!force && cached && Date.now() < cached.expiresAt - REFRESH_MARGIN_MS) return cached.token;

  const res = await fetch(`${base}/oauth/token`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      grant_type: 'client_credentials',
      client_id: NOCCIE_KEY_ID,
      client_secret: NOCCIE_KEY_SECRET,
    }),
  });
  if (!res.ok) throw new Error(`Token request failed: ${res.status} ${(await res.json().catch(() => ({}))).error ?? ''}`);

  const { access_token, expires_in } = await res.json();
  cached = { token: access_token, expiresAt: Date.now() + expires_in * 1000 };
  return cached.token;
}

async function apiGet(path, { retried = false, waited = false } = {}) {
  const res = await fetch(`${base}${path}`, { headers: { Authorization: `Bearer ${await getToken()}` } });

  if (res.status === 401 && !retried) {
    await getToken(true); // fetch ONE new token, then retry once
    return apiGet(path, { retried: true, waited });
  }
  if (res.status === 429 && !waited) {
    const seconds = Number(res.headers.get('retry-after')) || 5;
    await new Promise((resolve) => setTimeout(resolve, seconds * 1000)); // reuse the same token
    return apiGet(path, { retried, waited: true });
  }
  if (!res.ok) throw new Error(`API request failed: ${res.status}`); // a second 401 is a real error
  return res.json();
}

async function main() {
  const { products } = await apiGet(`/tenant/${NOCCIE_TENANT_ID}/product?type=service,set`);
  console.log(`Received ${products.length} products`);
}

main().catch((err) => {
  console.error(err.message);
  process.exit(1);
});

Scopes and revoking keys

A scope is a permission on a key, such as view_product. A call only works when the key has the scope that the endpoint requires. The required scope is listed per endpoint.

The scopes are not stored in the token. NOCCIE checks the key on every request. That means:

  • When a key is revoked, its tokens stop working immediately, even if they have not expired yet.
  • When the scopes of a key change, the change applies immediately to existing tokens.
  • When a key expires, its tokens stop working at the same moment.

Tokens only work on endpoints that are explicitly enabled for API tokens, and only for the tenant of the key.

Errors

Token endpoint

Errors from POST /oauth/token have the form {"error": "<code>"}.

StatuserrorMeaning
400invalid_requestMalformed request, missing or badly formatted credentials, or a body that does not exactly match the Basic header.
400unsupported_grant_typegrant_type is not client_credentials.
401invalid_clientThe key is not accepted. The response is the same for an unknown key, a wrong secret, a revoked key and an expired key.
429rate_limitedToo many token requests. Wait, and cache your token.
500 or 502server_errorTemporary error. Retry later.

API endpoints

StatusMeaning
400A query parameter is not allowed or invalid. The response has error: "invalid_request" and usually an issues list with the path and code of each problem. A request with an invalid URL returns only error.
401The token is missing, invalid or expired, the key was revoked or expired, or the token belongs to a different tenant than the one in the path. Request a new token. If that fails with invalid_client, the key is no longer valid.
403not_allowed: the endpoint, or the scope it needs, is not permitted for this key.

Security

  • Keep the key secret on your server. Never put it in a browser, mobile app, public repository or log file.
  • Use one key per integration, with only the scopes it needs, so you can revoke one without affecting the others.
  • Rotate a key by creating a new key, switching your integration to it, and then revoking the old key.
  • If a secret may have leaked, revoke the key right away. Its tokens stop working immediately.
  • Set an expiry date on keys that are only needed temporarily.

Laatst bijgewerkt op 8 oktober 2026

Op deze pagina