Error handling
The SDK translates every API error into a typed exception. Catch specific exception types to handle each failure mode correctly — don't rely on string matching error messages.
Error response schema
Every error response from the CaseForge API follows the same JSON shape:
{
"error": {
"code": "case_not_found", // Machine-readable error code
"message": "No case with ID 'case_abc123' was found in this workspace.",
"param": "case_id", // Which parameter caused the error (if applicable)
"doc_url": "https://docs.caseforge.io/errors/case_not_found"
},
"request_id": "req_7x2k4m9z" // Include this when contacting support
}
SDK exception hierarchy
All SDK exceptions extend CaseForgeError. Catch the base class to handle all API errors, or catch a specific subclass to handle a particular failure mode:
CaseForgeError # Base class — all SDK errors
├── APIError # HTTP 4xx/5xx from the API
│ ├── AuthenticationError # 401 — invalid or expired credentials
│ ├── PermissionError # 403 — insufficient scope
│ ├── NotFoundError # 404 — resource not found
│ ├── ConflictError # 409 — duplicate resource or state conflict
│ ├── ValidationError # 422 — request body failed validation
│ ├── RateLimitError # 429 — rate limit exceeded
│ └── ServerError # 500/503 — CaseForge infrastructure error
├── NetworkError # Connection timeout, DNS failure, TLS error
└── TimeoutError # Request exceeded the configured timeout
CaseForgeError // Base class — all SDK errors
├── APIError // HTTP 4xx/5xx from the API
│ ├── AuthenticationError // 401
│ ├── PermissionError // 403
│ ├── NotFoundError // 404
│ ├── ConflictError // 409
│ ├── ValidationError // 422
│ ├── RateLimitError // 429
│ └── ServerError // 500/503
├── NetworkError // Connection-level errors
└── TimeoutError // Request timed out
Catching exceptions
import caseforge
from caseforge.exceptions import (
ValidationError, NotFoundError, RateLimitError, CaseForgeError
)
client = caseforge.Client()
try:
case = client.cases.get("case_invalid")
except NotFoundError as e:
# 404 — case ID doesn't exist or belongs to a different workspace
print(f"Case not found: {e.error_code} — {e.message}")
except ValidationError as e:
# 422 — the request body failed server-side validation
print(f"Validation failed on '{e.param}': {e.message}")
except RateLimitError as e:
# 429 — back off and retry after e.retry_after seconds
print(f"Rate limited. Retry after {e.retry_after}s")
except CaseForgeError as e:
# Catch-all for any SDK error
print(f"SDK error [{e.request_id}]: {e}")
import CaseForge, {
NotFoundError, ValidationError, RateLimitError, CaseForgeError
} from '@caseforge/sdk';
const client = new CaseForge();
try {
const aCase = await client.cases.get('case_invalid');
} catch (e) {
if (e instanceof NotFoundError) {
console.error(`Case not found: ${e.errorCode}`);
} else if (e instanceof ValidationError) {
console.error(`Validation failed on '${e.param}': ${e.message}`);
} else if (e instanceof RateLimitError) {
console.error(`Rate limited. Retry after ${e.retryAfter}s`);
} else if (e instanceof CaseForgeError) {
console.error(`SDK error [${e.requestId}]: ${e.message}`);
}
}
HTTP status code reference
| Status | SDK exception | When it occurs | Resolution |
|---|---|---|---|
| 400 | APIError | Malformed request — missing fields, wrong content type. | Check request body structure against the API reference. |
| 401 | AuthenticationError | Missing, invalid, or expired credential. | Verify your API key or refresh your OAuth token. Check for stray whitespace when setting env vars. |
| 403 | PermissionError | Valid credentials, but the token lacks the required scope. | Add the needed scope when registering your OAuth application, or use a key with Workspace Admin role. |
| 404 | NotFoundError | The requested resource doesn't exist, or belongs to a different workspace. | Verify the resource ID. Confirm your API key is scoped to the correct workspace. |
| 409 | ConflictError | Duplicate resource — creating a case for a subject that already has an open case of the same type. | Retrieve the existing resource instead of creating a new one, or close the existing case first. |
| 422 | ValidationError | Request body is syntactically valid JSON, but fails business rule validation. | Read error.param to identify which field failed. Common causes: priority out of range, date in the past, or an unsupported case_type. |
| 429 | RateLimitError | Too many requests on the Sandbox tier, or a burst above your plan limit. | Respect the Retry-After response header (e.retry_after in the SDK). Implement exponential backoff for high-volume pipelines. |
| 500 | ServerError | CaseForge infrastructure error. | Retry with exponential backoff. If the error persists beyond 15 minutes, check the CaseForge status page at status.caseforge.io. |
| 503 | ServerError | Service temporarily unavailable — typically a brief maintenance window. | Retry after the interval in the Retry-After header. |
Retry logic
The SDK includes built-in retry logic for transient failures. By default, it retries 429 and 503 responses up to two times using exponential backoff with jitter.
Configure retry behavior
import caseforge
client = caseforge.Client(
max_retries=3, # Retry up to 3 times (default: 2)
timeout=30.0, # Seconds before a request times out (default: 60)
retry_on_status=[429, 503], # Which status codes trigger a retry
)
import CaseForge from '@caseforge/sdk';
const client = new CaseForge({
maxRetries: 3, // default: 2
timeout: 30_000, // milliseconds, default: 60000
retryOnStatus: [429, 503], // default
});
Client errors (400, 401, 403, 404, 422) indicate a problem with the request. Retrying them won't succeed and wastes rate limit budget. Only retry 429 and 5xx responses.
Idempotency keys
For write operations (case creation, SAR filing), include an idempotency key to make retries safe. If a request fails and you retry it with the same key, the API returns the result of the original request — rather than creating a duplicate resource.
import uuid
import caseforge
client = caseforge.Client()
# Generate a key per request attempt — store it so you can retry with the same key
idempotency_key = str(uuid.uuid4())
case = client.cases.create(
subject_id="cust_7a2b4c",
case_type="transaction_monitoring",
priority="high",
summary="Structuring activity",
idempotency_key=idempotency_key, # Pass as a keyword argument
)
import { randomUUID } from 'crypto';
import CaseForge from '@caseforge/sdk';
const client = new CaseForge();
const idempKey = randomUUID(); // Store this for retry
const aCase = await client.cases.create(
{
subjectId: 'cust_7a2b4c',
caseType: 'transaction_monitoring',
priority: 'high',
summary: 'Structuring activity',
},
{ idempotencyKey: idempKey }, // Pass as second argument
);