Documentation
Authentication
OAuth2 Client Credentials: how a token is obtained, how long it lasts and when it stops working.
The API uses OAuth2 Client Credentials (RFC 6749 §4.4), the flow designed for one program to talk to another with nobody in front of the screen. There is no consent screen and no redirects: two secrets are exchanged for a short-lived token.
client_id + client_secret
│
▼
POST https://api.niucore.com/api/oauth/token
│
▼
access_token (JWT, ~1 h)
│
▼
Authorization: Bearer … → https://api.niucore.com/api/v1/…The credentials
A credential is two values. The client_id (nc_…) is public and identifies the integration; the client_secret (ncs_…) is private and is shown once, when created or rotated.
Only its HMAC-SHA256 with a *pepper* stored outside the database is kept. Not even a full database dump is enough to authenticate — and that is why there is no way to recover it: if it is lost, you rotate it.
A machine secret
The client_secret belongs in a secret manager or a server environment variable. Never in a frontend, a mobile app, a repository or a URL: whoever holds it can act as the owning user with the credential's full scope.
Requesting a token
Both standard client-authentication methods are accepted, but not both at once: sending credentials through Authorization: Basic and in the body at the same time returns invalid_request, because it leaves ambiguous which one wins.
| Method | How |
|---|---|
client_secret_basic | Authorization: Basic base64(client_id:client_secret). Recommended: the secret never appears in the body. |
client_secret_post | client_id and client_secret as form fields. |
curl -sS -X POST 'https://api.niucore.com/api/oauth/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-u "$NIUCORE_CLIENT_ID:$NIUCORE_CLIENT_SECRET" \
-d 'grant_type=client_credentials' \
-d 'scope=chat:read chat:write'Tip
Cache the token and refresh it slightly before expires_in. Requesting a new one per call works, but it burns the client_id bucket (60 requests per minute) for nothing.
The scope parameter
scope is optional and only narrows. Without it, the token carries every scope the credential currently holds; with it, only the ones you ask for. Requesting a scope the credential does not hold returns invalid_scope and names which ones failed.
Asking for the minimum is good practice: if the token leaks, the damage is bounded to what that process actually did.
Using the token
GET https://api.niucore.com/api/v1/chats HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Accept-Language: esThe token is a signed JWT, but treat it as opaque: its claims are an internal detail and may change. If you need to know whether it is still valid, use `POST /api/oauth/introspect`.
When it stops working
A token is valid until any of these happens. All of them produce 401 with WWW-Authenticate: Bearer error="invalid_token", and all are fixed by requesting a new token — except the last two, which need human intervention.
| Cause | What to do |
|---|---|
It expired (expires_in). | Request another. |
| The credential's secret was rotated. | Update the client_secret and request another. Old tokens die on the next request, without waiting to expire. |
| The token was revoked (RFC 7009). | Request another. |
| The credential was revoked or expired. | Create a new one from the application. Requesting a token returns invalid_client. |
| The owning user was deactivated, changed company or lost the role. | The credential loses its basis. A new one with a current owner is required. |
Anti-enumeration
A nonexistent client_id, a wrong secret and a revoked credential all return the same invalid_client, and the server does the same cryptographic work in all three cases. They cannot be told apart by response or by timing.
Discovery
If your OAuth client can read RFC 8414 metadata, point it at https://api.niucore.com/.well-known/oauth-authorization-server and it configures itself: endpoints, grants, authentication methods and scope catalog.