Help us improve
Share bugs, ideas, or general feedback.
From ninjaone-rmm
Provides patterns for NinjaOne REST API including OAuth 2.0 authentication, cursor-based pagination, rate limiting, and error handling. Use for all NinjaOne integrations.
npx claudepluginhub wyre-technology/msp-claude-plugins --plugin ninjaone-rmmHow this skill is triggered — by the user, by Claude, or both
Slash command
/ninjaone-rmm:api-patternsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
The NinjaOne Public API uses OAuth 2.0 for authentication and provides RESTful endpoints for all platform operations.
Provides ConnectWise Automate REST API patterns: integrator/user authentication, token management, OData filtering, pagination, rate limiting, and error handling.
Manages NinjaOne organizations: creates, lists, configures locations, policy mappings, and node approvals for MSP client device containers.
Provides GraphQL API patterns for SuperOps.ai: Bearer token auth, query/mutation building, cursor pagination, rate limiting, and error handling.
Share bugs, ideas, or general feedback.
The NinjaOne Public API uses OAuth 2.0 for authentication and provides RESTful endpoints for all platform operations.
| 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 control
Response:
{
"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/json
POST /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=abc123xyz
let 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 |