NinjaOne API Patterns — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited NinjaOne API Patterns (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
The NinjaOne Public API uses OAuth 2.0 for authentication and provides RESTful endpoints for all platform operations.
OAuth, rate limits, and cursors, and none of them agree. Use atera-api-patterns, ncentral-api-patterns, datto-rmm-api-patterns, syncro-api-patterns, or connectwise-automate-api-patterns.
call succeeds** — that is a gateway-connection problem rather than a NinjaOne API problem; use shared-skills-wyre-gateway-troubleshooting.
entities themselves use ninjaone-devices, ninjaone-organizations, ninjaone-alerts, or ninjaone-tickets.
| Region | Base URL |
|---|---|
| United States | https://app.ninjarmm.com |
| European Union | https://eu.ninjarmm.com |
| Oceania | https://oc.ninjarmm.com |
Use the base URL matching your NinjaOne instance region.
NinjaOne uses OAuth 2.0 with the following scopes:
monitoring - Read monitoring datamanagement - Manage devices and organizationscontrol - Remote control capabilitiesPOST /oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&scope=monitoring management controlResponse:
{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "monitoring management control"
}Include in all API requests:
GET /api/v2/organizations
Authorization: Bearer eyJ...Tokens expire after the expires_in period. Request a new token before expiration.
GET /api/v2/organizations
Authorization: Bearer {token}
Accept: application/jsonPOST /api/v2/organizations
Authorization: Bearer {token}
Content-Type: application/json
Accept: application/json
{
"name": "New Organization",
"description": "Description here"
}PATCH /api/v2/device/{id}
Authorization: Bearer {token}
Content-Type: application/json
{
"displayName": "Updated Name"
}NinjaOne uses cursor-based pagination:
GET /api/v2/organizations?pageSize=50{
"results": [...],
"pageInfo": {
"hasNextPage": true,
"endCursor": "abc123xyz"
}
}GET /api/v2/organizations?pageSize=50&after=abc123xyzlet cursor = null;
let allResults = [];
do {
const url = cursor
? `/api/v2/organizations?pageSize=100&after=${cursor}`
: '/api/v2/organizations?pageSize=100';
const response = await fetch(url, { headers });
const data = await response.json();
allResults = allResults.concat(data.results);
cursor = data.pageInfo.hasNextPage ? data.pageInfo.endCursor : null;
} while (cursor);NinjaOne implements rate limiting to ensure API stability:
Watch for these response headers:
X-RateLimit-Limit - Max requests per windowX-RateLimit-Remaining - Requests remainingX-RateLimit-Reset - Window reset timeWhen rate limited:
{
"error": "rate_limit_exceeded",
"message": "Too many requests",
"retry_after": 60
}| Code | Meaning | Action |
|---|---|---|
| 200 | Success | Process response |
| 201 | Created | Resource created successfully |
| 204 | No Content | Success, no body |
| 400 | Bad Request | Check request format |
| 401 | Unauthorized | Refresh token |
| 403 | Forbidden | Check permissions |
| 404 | Not Found | Verify resource exists |
| 409 | Conflict | Resource conflict |
| 422 | Validation Error | Check field values |
| 429 | Rate Limited | Wait and retry |
| 500 | Server Error | Retry with backoff |
{
"error": "validation_error",
"message": "Invalid field value",
"details": {
"field": "name",
"issue": "Required field missing"
}
}async function makeRequest(url, options) {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || 60;
await sleep(retryAfter * 1000);
return makeRequest(url, options);
}
if (response.status === 401) {
await refreshToken();
return makeRequest(url, options);
}
if (!response.ok) {
const error = await response.json();
throw new ApiError(error.message, response.status);
}
return response.json();
}PUT /api/v2/webhook
Content-Type: application/json
{
"url": "https://your-server.com/webhook",
"events": ["ALERT_TRIGGERED", "DEVICE_OFFLINE"]
}DELETE /api/v2/webhook| Event | Description |
|---|---|
ALERT_TRIGGERED | New alert created |
ALERT_CLEARED | Alert resolved |
DEVICE_ONLINE | Device connected |
DEVICE_OFFLINE | Device disconnected |
monitoring, management, control)~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.