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-rmmThis skill uses the workspace's default tool permissions.
The NinjaOne Public API uses OAuth 2.0 for authentication and provides RESTful endpoints for all platform operations.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides agent creation for Claude Code plugins with file templates, frontmatter specs (name, description, model), triggering examples, system prompts, and best practices.
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 |