Errors

The EmbayLMS API uses standard HTTP status codes and returns a consistent error envelope on every failure. Errors never include internal stack traces or database details.


Error Envelope

{
  "data": null,
  "meta": null,
  "error": {
    "code": 404,
    "message": "Tenant not found"
  }
}
FieldTypeDescription
error.codeintegerNumeric code — always equal to the HTTP status of the response
error.messagestringHuman-readable description, safe to log and display

On success, error is null and data carries the payload. There is no details object — branch on error.code (or the HTTP status, they are the same) and use error.message for diagnostics.


Status Codes

HTTP Status / error.codeWhen Used
200 OKSuccessful GET
400 Bad RequestInvalid request parameters (e.g. missing required query parameter)
401 UnauthorizedMissing, invalid, revoked, or expired API key (response includes WWW-Authenticate: Bearer)
403 ForbiddenValid key but insufficient scope (e.g. missing read), or API access not included in your plan
404 Not FoundResource does not exist in this tenant, or the endpoint is not implemented (see the “Planned” banners)
429 Too Many RequestsPer-minute rate limit or monthly usage cap exceeded — retry after Retry-After seconds. See Rate Limits
500 Internal Server ErrorUnexpected server error — retry with backoff; contact support if persistent
503 Service UnavailableTemporary outage or maintenance

Write endpoints additionally use 201 Created (successful create), 204 No Content (successful delete), 207 Multi-Status (bulk partial errors — see below), and 409 Conflict (e.g. duplicate email on user creation). Calling a still-planned endpoint (see the “Planned” banners) returns 404.


Error Handling Examples

Node.js

async function listUsers() {
  const response = await fetch('https://{your-subdomain}.embaylms.com/api/v1/users', {
    headers: {
      'Authorization': `Bearer ${process.env.EMBAYLMS_API_KEY}`,
    },
  });
 
  const result = await response.json();
 
  if (!response.ok) {
    const { error } = result;
 
    switch (error.code) {
      case 401:
        throw new Error('API key missing, invalid, or revoked — rotate the key');
      case 403:
        throw new Error(`Forbidden: ${error.message}`); // missing scope or plan
      case 429: {
        const retryAfter = parseInt(response.headers.get('Retry-After') || '1', 10);
        throw new RateLimitError(retryAfter);
      }
      default:
        throw new Error(`API error ${error.code}: ${error.message}`);
    }
  }
 
  return result.data;
}

Python

import httpx
 
class EmbayLMSError(Exception):
    def __init__(self, code, message):
        self.code = code
        self.message = message
        super().__init__(f"{code}: {message}")
 
def list_users(client):
    response = client.get("/users")
 
    if response.status_code == 200:
        return response.json()["data"]
 
    error = response.json().get("error") or {}
    code = error.get("code", response.status_code)
 
    if code == 429:
        retry_after = int(response.headers.get("Retry-After", "1"))
        raise EmbayLMSError(429, f"Rate limited — retry after {retry_after}s")
 
    raise EmbayLMSError(code, error.get("message", "Unknown error"))

Validation and Bulk Errors

When input validation fails on a write endpoint, the 400 response’s error.message identifies the failing field(s).

Live: POST /enrollments/bulk uses 207 Multi-Status when some rows fail, reporting individual errors per row — each row runs independently, so one bad row never fails the batch:

{
  "data": {
    "created": 3,
    "skipped": 2,
    "errors": [
      {
        "index": 3,
        "code": "course_not_found",
        "message": "Course not found"
      },
      {
        "index": 7,
        "code": "duplicate",
        "message": "User is already enrolled in this course"
      }
    ]
  },
  "meta": null,
  "error": null
}

The HTTP status is 207 when there are partial errors, 201 when all rows succeed. The top-level error stays null for bulk endpoints — per-row errors are in data.errors (index is the zero-based position in the submitted array).

⚠️ Planned — not yet available. POST /users/bulk is still part of the planned API surface and returns 404 today; when it ships it will follow the same 207 Multi-Status per-row error pattern.