API Reference
Quick start - What it's for — submit, query, and update Nembl
resources from external systems (CI/CD, scripts, third-party integrations) -
When to use it — you have a system that needs to create requests, read
workflow status, or sync data into/out of Nembl - Get started in 10 min —
issue an API Key, pass it as the x-api-key
header, hit GET /v1/services to test
The Nembl REST API provides programmatic access to services, requests, workflows, users, and other platform resources. Use the API to build integrations, automate processes, and extend the platform.
Base URL
All API requests use the following base URL:
https://api.nembl.devopspolis.com/v1All endpoints require HTTPS. HTTP requests are rejected.
Authentication
Authenticate API requests using an API key passed in the x-api-key header:
curl -X GET https://api.nembl.devopspolis.com/v1/services \
-H "x-api-key: nmbl_live_abc123xyz"API keys are scoped to your company and carry the permissions of the user or service account that created them. See Authentication for details on creating and managing API keys.
Tenant Scoping (companyId)
You'll notice the API has no companyId query parameter or header. Don't add one — it would be ignored. Tenant scoping is enforced server-side from the API key, not the client request.
How it works
Every API key is bound to exactly one Company at creation time (ApiKey.companyId). When Nembl receives an authenticated request:
- The
x-api-keyheader is hashed and looked up to find the matchingApiKeyrow - The row carries
userId,companyId, andscope— the server readscompanyIdfrom there - Every database query in the handler is filtered by that
companyId(every Nembl table is tenant-scoped —Service,Workflow,Request,User,Team, etc. all have acompanyIdforeign key) - The client never names a company; it gets back only rows belonging to its key's Company
This makes cross-tenant data exposure structurally impossible from the API surface — even a malformed or hostile request can't widen the scope, because the scope is derived from the credential, not from the request payload.
What this means for you
- A single API key works against one Company. To submit requests on behalf of two Companies, create one key per Company.
- IDs returned by the API (
svc_abc123,req_def456, …) are globally unique cuids — but you can only resolve those that belong to your Company. Looking up an ID from a different Company returns404, not403(Nembl declines to confirm or deny existence across tenants). - The same applies to session-based auth in the web UI, where the resolution goes JWT → user → AccountMember (with re-check) → companyId. The mechanism differs; the scoping guarantee is identical.
- For B2B requests where you legitimately need data about a partner Company, use the cross-Company request resources — they expose only the slice that the partner has shared with you, not full cross-tenant access. See B2B Requests.
If you ever see an API response containing rows from a Company that isn't your key's Company, file a security report immediately — that's a tenant-isolation bug, not a feature.
Rate Limits
API requests are rate-limited per API key to ensure platform stability.
| Plan | Requests per minute | Requests per day |
|---|---|---|
| Free / Pro | 60 | 10,000 |
| Starter | 120 | 50,000 |
| Growth | 300 | 200,000 |
| Business | 600 | 1,000,000 |
| Enterprise | Custom | Custom |
When you exceed the rate limit, the API returns a 429 Too Many Requests response with a Retry-After header indicating how many seconds to wait.
{
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded. Retry after 12 seconds.",
"retryAfter": 12
}Common Response Format
All API responses follow a consistent JSON structure.
Successful Responses
Single resource:
{
"data": {
"id": "svc_abc123",
"name": "IT Support",
"status": "active",
"createdAt": "2026-01-15T09:00:00Z",
"updatedAt": "2026-03-10T14:30:00Z"
}
}List of resources (paginated):
{
"data": [
{ "id": "svc_abc123", "name": "IT Support" },
{ "id": "svc_def456", "name": "HR Services" }
],
"pagination": {
"page": 1,
"pageSize": 20,
"totalPages": 3,
"totalCount": 52
}
}Error Responses
{
"error": "validation_error",
"message": "The 'name' field is required.",
"details": [{ "field": "name", "message": "Required field is missing" }]
}HTTP Status Codes
| Code | Meaning |
|---|---|
200 | Success |
201 | Resource created |
204 | Success with no content (e.g., delete) |
400 | Bad request (validation error) |
401 | Unauthorized (missing or invalid API key) |
403 | Forbidden (insufficient permissions) |
404 | Resource not found |
409 | Conflict (e.g., duplicate slug) |
429 | Rate limit exceeded |
500 | Internal server error |
Pagination
List endpoints support pagination with query parameters:
| Parameter | Default | Description |
|---|---|---|
page | 1 | Page number (1-indexed) |
pageSize | 20 | Items per page (max 100) |
curl "https://api.nembl.devopspolis.com/v1/requests?page=2&pageSize=50" \
-H "x-api-key: nmbl_live_abc123xyz"Filtering and Sorting
List endpoints support filtering and sorting:
# Filter by status
curl "https://api.nembl.devopspolis.com/v1/requests?status=IN_PROGRESS" \
-H "x-api-key: nmbl_live_abc123xyz"
# Sort by creation date (descending)
curl "https://api.nembl.devopspolis.com/v1/requests?sort=-createdAt" \
-H "x-api-key: nmbl_live_abc123xyz"
# Combine filters
curl "https://api.nembl.devopspolis.com/v1/requests?status=PENDING&priority=HIGH&sort=-createdAt" \
-H "x-api-key: nmbl_live_abc123xyz"Prefix a sort field with - for descending order.
Core Endpoints
| Endpoint | Methods | Description |
|---|---|---|
/v1/services | GET, POST | List and create services |
/v1/services/{id} | GET, PATCH, DELETE | Get, update, or delete a service |
/v1/services/{id}/offerings | GET, POST | List and create offerings for a service |
/v1/requests | GET, POST | List and create requests |
/v1/requests/{id} | GET, PATCH | Get or update a request |
/v1/workflows | GET, POST | List and create workflows |
/v1/workflows/{id} | GET, PATCH, DELETE | Get, update, or delete a workflow |
/v1/users | GET | List company members |
/v1/users/{id} | GET, PATCH | Get or update a user |
/v1/teams | GET, POST | List and create teams |
/v1/organizations | GET, POST | List and create organizations |
/v1/secrets | GET, POST | List and create secrets (value not returned) |
/v1/billing/credits | GET | View credit balance and history |
/v1/audit | GET | Query audit log entries |
Creating a Request (Example)
curl -X POST https://api.nembl.devopspolis.com/v1/requests \
-H "x-api-key: nmbl_live_abc123xyz" \
-H "Content-Type: application/json" \
-d '{
"serviceId": "svc_abc123",
"offeringId": "off_xyz789",
"title": "New laptop for onboarding",
"priority": "MEDIUM",
"fields": {
"employeeName": "Jane Smith",
"department": "Engineering",
"laptopType": "MacBook Pro 16"
}
}'Response:
{
"data": {
"id": "req_new456",
"serviceId": "svc_abc123",
"offeringId": "off_xyz789",
"title": "New laptop for onboarding",
"status": "PENDING",
"priority": "MEDIUM",
"createdAt": "2026-04-01T10:00:00Z"
}
}SDKs and Libraries
Official SDKs are planned for:
- TypeScript/JavaScript (
@nembl/sdk) - Python (
nembl-python)
In the meantime, use any HTTP client to interact with the REST API directly.