Syncro API Patterns — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Syncro 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 Syncro MSP API provides access to tickets, customers, assets, invoices, and more. This skill covers authentication, pagination, rate limiting, error handling, and best practices for API integration.
Syncro uses Bearer token authentication with API keys:
GET /api/v1/tickets
Authorization: Bearer YOUR_API_KEY
Content-Type: application/jsonexport SYNCRO_API_KEY="your-api-key-here"
export SYNCRO_SUBDOMAIN="your-subdomain" # e.g., "acme" for acme.syncromsp.comThe API base URL uses your subdomain:
https://{subdomain}.syncromsp.com/api/v1/Example:
https://acme.syncromsp.com/api/v1/ticketsSyncro uses page-based pagination:
GET /api/v1/tickets?page=1
GET /api/v1/tickets?page=2
GET /api/v1/tickets?page=3| Parameter | Description | Default |
|---|---|---|
page | Page number (1-based) | 1 |
Responses include pagination information:
{
"tickets": [...],
"meta": {
"total_entries": 156,
"total_pages": 7,
"page": 1,
"per_page": 25
}
}async function fetchAllTickets(filter = {}) {
const allItems = [];
let page = 1;
let hasMore = true;
while (hasMore) {
const response = await fetch(
`https://${SUBDOMAIN}.syncromsp.com/api/v1/tickets?page=${page}`,
{
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json'
}
}
);
const data = await response.json();
allItems.push(...data.tickets);
hasMore = page < data.meta.total_pages;
page++;
// Respect rate limits
await sleep(350); // ~170 req/min to stay under 180/min
}
return allItems;
}Syncro enforces a rate limit of 180 requests per minute per IP address.
When rate limited, you receive:
HTTP/1.1 429 Too Many Requests
Retry-After: 30{
"error": "Rate limit exceeded. Please wait before making more requests."
}async function requestWithRetry(url, options, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || 30;
const jitter = Math.random() * 1000;
console.log(`Rate limited. Waiting ${retryAfter}s...`);
await sleep(retryAfter * 1000 + jitter);
continue;
}
return response;
} catch (error) {
if (attempt === maxRetries - 1) throw error;
// Exponential backoff with jitter
const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
await sleep(delay);
}
}
}For bulk operations, throttle requests:
async function batchProcess(items, processFunc, delayMs = 350) {
const results = [];
for (const item of items) {
const result = await processFunc(item);
results.push(result);
await sleep(delayMs);
}
return results;
}Single resource:
GET /api/v1/tickets/12345
Authorization: Bearer YOUR_API_KEYList with filters:
GET /api/v1/tickets?customer_id=123&status=open&page=1
Authorization: Bearer YOUR_API_KEYPOST /api/v1/tickets
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"customer_id": 12345,
"subject": "New support request",
"status": "New",
"priority": "Medium"
}PUT /api/v1/tickets/12345
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
{
"status": "Resolved",
"priority": "Low"
}DELETE /api/v1/contacts/67890
Authorization: Bearer YOUR_API_KEY| Parameter | Description | Example |
|---|---|---|
page | Page number | page=2 |
query | Search term | query=email |
customer_id | Filter by customer | customer_id=123 |
date_from | Start date | date_from=2024-01-01 |
date_to | End date | date_to=2024-01-31 |
mine | Current user only | mine=true |
status | Status filter | status=open |
| Code | Meaning | Action |
|---|---|---|
| 200 | Success | Process response |
| 201 | Created | Resource created |
| 400 | Bad Request | Check request format |
| 401 | Unauthorized | Verify API key |
| 403 | Forbidden | Check permissions |
| 404 | Not Found | Resource doesn't exist |
| 422 | Unprocessable | Validation failed |
| 429 | Rate Limited | Wait and retry |
| 500 | Server Error | Retry with backoff |
{
"error": "Validation failed",
"errors": [
{
"field": "customer_id",
"message": "is required"
}
]
}async function makeRequest(url, options) {
const response = await fetch(url, options);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
switch (response.status) {
case 401:
throw new Error('Invalid API key. Check your credentials.');
case 403:
throw new Error('Permission denied. Check API key permissions.');
case 404:
throw new Error('Resource not found.');
case 422:
const messages = errorData.errors?.map(e => `${e.field}: ${e.message}`).join(', ');
throw new Error(`Validation failed: ${messages}`);
case 429:
throw new Error('Rate limited. Try again later.');
default:
throw new Error(`API error: ${response.status}`);
}
}
return response.json();
}| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/tickets | List tickets |
| POST | /api/v1/tickets | Create ticket |
| GET | /api/v1/tickets/{id} | Get ticket |
| PUT | /api/v1/tickets/{id} | Update ticket |
| POST | /api/v1/tickets/{id}/comment | Add comment |
| POST | /api/v1/tickets/{id}/timer | Timer operations |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/customers | List customers |
| POST | /api/v1/customers | Create customer |
| GET | /api/v1/customers/{id} | Get customer |
| PUT | /api/v1/customers/{id} | Update customer |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/contacts | List contacts |
| POST | /api/v1/contacts | Create contact |
| GET | /api/v1/contacts/{id} | Get contact |
| PUT | /api/v1/contacts/{id} | Update contact |
| DELETE | /api/v1/contacts/{id} | Delete contact |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/customer_assets | List assets |
| POST | /api/v1/customer_assets | Create asset |
| GET | /api/v1/customer_assets/{id} | Get asset |
| PUT | /api/v1/customer_assets/{id} | Update asset |
| DELETE | /api/v1/customer_assets/{id} | Delete asset |
| GET | /api/v1/customer_assets/{id}/patches | Get patches |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/invoices | List invoices |
| POST | /api/v1/invoices | Create invoice |
| GET | /api/v1/invoices/{id} | Get invoice |
| PUT | /api/v1/invoices/{id} | Update invoice |
| POST | /api/v1/invoices/{id}/email | Email invoice |
| POST | /api/v1/invoices/{id}/payments | Record payment |
# List tickets
curl -X GET "https://acme.syncromsp.com/api/v1/tickets?page=1" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json"
# Create ticket
curl -X POST "https://acme.syncromsp.com/api/v1/tickets" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_id": 12345,
"subject": "New ticket",
"status": "New",
"priority": "Medium"
}'~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.